// Hyperbolic Rogue

// Copyright (C) 2011-2013 Zeno Rogue, see 'hyper.cpp' for details

#define DEB(x) // printf("%s\n", x); fflush(stdout);

time_t timerstart;
int turncount;

bool showoff = false;
bool safety = false;
int showid = 0;

eLand showlist[10] = {
  laHell, laRlyeh, laAlchemist, laGraveyard, laCaves, laDesert, laIce, laJungle, laMotion, laMirror
  };

eLand firstland = laIce;

extern void DEBT(const char *buf);

#define DEBT(x) // printf("%s\n", x);

// game state
int items[ittypes], kills[motypes], explore[10], landcount[landtypes];
bool playerdead = false;  // obsolete
bool playermoved = true;  // center on the PC?
bool flipplayer = true;   // flip the player image after move, do not flip after attack
int  cheater = 0;         // did the player cheat?

#define INF  9999
#define INFD 20

vector<cell*> dcal;   // queue for cpdist
vector<cell*> pathq;  // queue for pathdist

vector<cell*> pathqm; // list of monsters to move (pathq restriced to monsters)

// monsters of specific types to move
vector<cell*> worms, ivies, ghosts, golems, mirrors;  

vector<cell*> temps;  // temporary changes during bfs
vector<eMonster> tempval;  // restore temps


int first7;           // the position of the first monster at distance 7 in dcal

cellwalker cwt; // player character position

bool isIcyLand(cell *c) {
  return c->land == laIce || c->land == laCocytus;
  }

void initcell(cell *c) {
  c->mpdist = INFD;   // minimum distance from the player, ever
  c->cpdist = INFD;   // current distance from the player
  c->pathdist = INFD; // current distance from the player, along paths (used by yetis)
  c->heat = 0;
  c->wall  = waNone;
  c->item  = itNone;
  c->monst = moNone;
  c->bardir = NODIR;
  c->land = laNone;
  c->tmp = -1;
  c->ligon = 0;
  }

int puregold() {
  return 
    items[itDiamond] + items[itGold] + items[itSpice] + items[itEmerald] + items[itElixir] +
    items[itShard] + items[itBone] + items[itStatue] + items[itHell] + items[itFeather] +
    items[itHyperstone] + items[itOrbYendor] * 50;
  }

int gold() {
  return puregold();
  }

int maxgold() {
  int mg = 0;
  for(int i=itDiamond; i<=itFeather; i++) if(items[i] > mg) mg = items[i];
  return mg;
  }

bool hellUnlocked() {
  return 
    items[itDiamond] >= 10 && items[itGold] >= 10 && items[itSpice] >= 10 && 
    items[itEmerald] >= 10 && items[itElixir] >= 10 && items[itShard] >= 10 &&
    items[itBone] >= 10 && items[itStatue] >= 10 && items[itFeather] >= 10;
  }

int tkills() {
  return 
    kills[moYeti] + kills[moWolf] + 
    kills[moRanger] + kills[moTroll] + kills[moGoblin] +
    kills[moWorm] + kills[moDesertman] + kills[moIvyRoot] +
    kills[moMonkey] + kills[moEagle] + kills[moSlime] + kills[moSeep] +
    kills[moRunDog] + 
    kills[moCultist] + kills[moTentacle] + kills[moPyroCultist] + 
    kills[moLesser] + kills[moGreater] + 
    kills[moZombie] + kills[moGhost] + kills[moNecromancer];
  }

bool passable(cell *w, cell *from, bool monster_passable, bool mirror_passable) {
  if(w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item)
    return false;
  if(w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item)
    return false;
  if(w->wall == waMirror || w->wall == waCloud) return mirror_passable;
  if(w->wall == waNone || w->wall == waFloorA || w->wall == waFloorB || 
    w->wall == waCavefloor || w->wall == waFrozenLake) {
    if(w->monst) return monster_passable;
    return true;
    }
  return false;
  }

// eagles can go through lakes, chasms, and slime
bool eaglepassable(cell *w) {
  if(w->monst) return false;
  return
    w->wall == waNone || w->wall == waFloorA || w->wall == waFloorB || 
    w->wall == waCavefloor || w->wall == waFrozenLake || w->wall == waLake ||
    w->wall == waSulphur || w->wall == waSulphurC || w->wall == waChasm;
  }

bool isActiv(cell *c) {
  return c->wall == waThumper || c->wall == waBonfire;
  }

bool isMimic(cell *c) {
  return c->monst == moMirror || c->monst == moMimic;
  }

bool isFriendly(cell *c) {
  return c->monst == moMirror || c->monst == moMimic || c->monst == moGolem || c->monst == moGolemMoved;
  }

bool isIvy(cell *c) {
  return c->monst == moIvyRoot || c->monst == moIvyHead || c->monst == moIvyBranch || c->monst == moIvyWait ||
    c->monst == moIvyNext || c->monst == moIvyDead;
  }

bool isDemon(cell *c) {
  return c->monst == moLesser || c->monst == moLesserM || 
    c->monst == moGreater || c->monst == moGreaterM;
  }

bool isWorm(cell *c) {
  return c->monst == moWorm || c->monst == moWormtail || c->monst == moWormwait || 
    c->monst == moTentacle || c->monst == moTentacletail || c->monst == moTentaclewait;
  }

void useup(cell *c) {
  c->tmp--;
  if(c->tmp == 0) c->wall = waNone;
  }

bool isInactiveEnemy(cell *w) {
  if(w->monst == moWormtail || w->monst == moWormwait || w->monst == moTentacletail || w->monst == moTentaclewait)
    return true;
  if(w->monst == moLesserM || w->monst == moGreaterM)
    return true;
  if(w->monst == moIvyRoot || w->monst == moIvyWait || w->monst == moIvyNext || w->monst == moIvyDead)
    return true;
  return false;
  }

bool isActiveEnemy(cell *w, cell *killed) {
  if(w->monst == moNone || w == killed) return false;
  if(isFriendly(w)) return false;
  if(isInactiveEnemy(w)) return false;
  if(w->monst == moIvyHead || w->monst == moIvyBranch) {
    while(w != killed && w->mondir != NODIR) w = w->mov[w->mondir];
    return w != killed;
    }
  return true;
  }

bool isKillable(cell *c) {
  return c->monst != moShadow && !isWorm(c) && c->monst != moGreater && c->monst != moGreaterM;
  }

bool isNeighbor(cell *c1, cell *c2) {
  for(int i=0; i<c1->type; i++) if(c1->mov[i] == c2) return true;
  return false;
  }

// how many monsters are near
eMonster which;

bool mirrorkill(cell *c) {
  for(int t=0; t<c->type; t++) 
    if(c->mov[t] && isMimic(c->mov[t]) && c->mov[t]->mov[c->mov[t]->mondir] == c)
      return true;
  return false;
  }

int monstersnear(cell *c, cell *nocount = NULL, bool shielded = true) {
  int res = 0;
  if(shielded) {
    if(items[itOrbShield] > 1) return 0;
    if(items[itOrbSpeed]  && !(items[itOrbSpeed] & 1)) return 0;
    }
  for(int t=0; t<c->type; t++) 
    if(c->mov[t] && isActiveEnemy(c->mov[t], nocount) && !(nocount && mirrorkill(c->mov[t])))
      res++, which = c->mov[t]->monst;
  return res;
  }

// reduce c->mpdist to d; also generate the landscape

bool checkBarriersBack(cellwalker& bb);

#define BARLEV (ISANDROID?9:10)
// #define BARLEV 9

bool checkBarriersFront(cellwalker& bb) {
  if(bb.c->mpdist < BARLEV) return false;
  if(bb.c->bardir != NODIR) return false;
  if(bb.spin == 0) return true;

  if(1) for(int i=0; i<7; i++) {
    cellwalker bb2 = bb;
    cwspin(bb2, i); cwstep(bb2); cwspin(bb2, 4); cwstep(bb2);
    if(bb2.c->bardir != NODIR) return false;
    }

  cwstep(bb); cwspin(bb, 3); cwstep(bb); cwspin(bb, 3); cwstep(bb);
  return checkBarriersBack(bb);
  }

bool checkBarriersBack(cellwalker& bb) {
  if(bb.c->mpdist < BARLEV) return false;
  if(bb.c->bardir != NODIR) return false;
  if(bb.spin == 0 && bb.c->mpdist == INFD) return true;
  
  if(1) for(int i=0; i<7; i++) {
    cellwalker bb2 = bb;
    cwspin(bb2, i); cwstep(bb2); cwspin(bb2, 4); cwstep(bb2);
    if(bb2.c->bardir != NODIR) return false;
    }

  cwspin(bb, 3); cwstep(bb); cwspin(bb, 4); cwstep(bb); cwspin(bb, 3); 
  return checkBarriersFront(bb);
  }

void setbarrier(cell *c) {
  c->wall = waBarrier;
  c->land = laBarrier;
  }

void killIvy(cell *c) {
  if(c->monst == moIvyDead) return;
  for(int i=0; i<c->type; i++) if(c->mov[i])
    if(c->mov[i]->mondir == c->spn[i] && isIvy(c->mov[i]))
      killIvy(c->mov[i]);
  c->monst = moIvyDead;
  }

int buildIvy(cell *c, int children, int minleaf) {
  c->mondir = NODIR;
  c->monst = moIvyRoot;
  
  cell *child = NULL;

  int leaf = 0;
  int leafchild = 0;
  for(int i=0; i<c->type; i++) {
    createMov(c, i);
    if(passable(c->mov[i], c, false, false)) {
      if(children && !child) 
        child = c->mov[i], leafchild = buildIvy(c->mov[i], children-1, 5);
      else 
        c->mov[i]->monst = (leaf++) ? moIvyWait : moIvyHead,
        c->mov[i]->mondir = c->spn[i];
      }
    }
  
  leaf += leafchild;
  if(leaf < minleaf) {
    if(child) killIvy(child);
    killIvy(c);
    return 0;
    }
  else return leaf;
  }

bool isIcyWall(cell *c) {
  return c->wall == waNone || c->wall == waIcewall || c->wall == waFrozenLake || c->wall == waLake;
  }

void spill(cell* c, eWall t, int rad) {
  if(c->land != laAlchemist) return;
  c->wall = t;
  // destroy items...
  c->item = itNone;
  // cwt.c->item = itNone;
  if(rad) for(int i=0; i<c->type; i++) if(c->mov[i])
    spill(c->mov[i], t, rad-1);
  }

void degradeDemons() {
  addMessage("You feel more experienced in demon fighting!");
  int dcs = size(dcal);
  for(int i=0; i<dcs; i++) {
    cell *c = dcal[i];
    if(c->monst == moGreaterM) c->monst = moLesserM;
    if(c->monst == moGreater) c->monst = moLesser;
    }  
  }

void ivynext(cell *c);

void killMonster(cell *c) {
  DEB("killmonster");
  if(!c->monst) return;
  if(isWorm(c)) return;
  if(c->monst == moShadow) return;
  if(c->monst == moLesserM) c->monst = moLesser;
  if(c->monst == moGreater) c->monst = moLesser;
  if(c->monst == moGreaterM) c->monst = moLesser;
  kills[c->monst]++;
  

  if(c->monst == moTroll) {
    c->wall = waDeadTroll, c->item = itNone;
    for(int i=0; i<c->type; i++) if(c->mov[i]) c->mov[i]->item = itNone;
    }
  if(c->monst == moSlime) spill(c, c->wall, 2);
  if(c->monst == moShark) c->heat += 1;
  if(c->monst == moGreaterShark) c->heat += 10;
  if(c->monst == moCultist) c->heat += 3;
  if(c->monst == moPyroCultist) c->heat += 6;
  if(c->monst == moLesser) c->heat += 10;
  if(c->monst == moLesser && !(kills[c->monst] % 10))
    degradeDemons();
  if(isIvy(c)) {
    eMonster m = c->monst;
    /*if((m == moIvyBranch || m == moIvyHead) && c->mov[c->mondir]->monst == moIvyRoot)
      ivynext(c, moIvyNext); */
    killIvy(c);
    if(m == moIvyBranch || m == moIvyHead || m == moIvyNext) {
      int qty = 0;
      cell *c2 = c->mov[c->mondir];
      for(int i=0; i<c2->type; i++)
        if(c2->mov[i]->monst == moIvyWait && c2->mov[i]->mondir == c2->spn[i])
          qty++;
      if(c->mov[c->mondir]->monst == moIvyRoot || qty) {
        c->monst = moIvyNext;
        /* c->monst = moIvyHead;
        ivynext(c);
        if(c->monst == moIvyHead) c->monst = moIvyNext;
        else c->monst = moNone; */
        }
      else {
        c->mov[c->mondir]->monst = moIvyHead;
        }
      }
    }
  else c->monst = moNone;

  }

bool orbChance(cell *c, eLand usual, int chthere, int chcross) {
  if(c->land == usual) return rand() % chthere == 0;
  if(chcross && c->land == laCrossroads) return rand() % chcross == 0;
  return false;
  }

void buildBarrier(cell *c) {
  if(c->wall == waBarrier) return;
  if(c->mpdist == INFD) return;

  cellwalker bb(c, c->bardir); setbarrier(bb.c);
  cwstep(bb); setbarrier(bb.c);
  cwspin(bb, 2); cwstep(bb); bb.c->land = c->barleft; cwstep(bb);
  cwspin(bb, 2); cwstep(bb); bb.c->land = c->barright; cwstep(bb);
  cwspin(bb, 2); 
  
  cwspin(bb, 3); cwstep(bb); setbarrier(bb.c);
  cwspin(bb, 3); cwstep(bb);
  
  bb.c->bardir = bb.spin;
  bb.c->barleft = c->barright;
  bb.c->barright = c->barleft;
  buildBarrier(c);
  
  for(int a=-3; a<=3; a++) if(a) {
    bb.c = c; bb.spin = c->bardir; cwspin(bb, a); cwstep(bb); 
    bb.c->land = a > 0 ? c->barright : c->barleft;
    }

  bb.c = c; bb.spin = c->bardir;
  cwspin(bb, 3); cwstep(bb); cwspin(bb, 4); bb.c->land = c->barright; cwstep(bb); cwspin(bb, 3);
  bb.c->bardir = bb.spin;
  bb.c->barleft = c->barright;
  bb.c->barright = c->barleft;
  buildBarrier(c);
  }

void chasmify(cell *c) {
  c->wall = waChasm; c->item = itNone;
  int q = 0;
  cell *c2[10];
  for(int i=0; i<c->type; i++) if(c->mov[i] && c->mov[i]->mpdist > c->mpdist && c->mov[i]->land == laMotion)
    c2[q++] = c->mov[i];
  if(q) {
    cell *c3 = c2[rand() % q];
    c3->wall = waChasmD;
    }
  }

eLand getNewLand(eLand old) {
  eLand tab[32];
  int cnt = 0;
  
  // return (rand() % 2) ? laMotion : laJungle;

  // the basic lands, always available
  tab[cnt++] = laCrossroads;
  tab[cnt++] = laIce;
  tab[cnt++] = laDesert;
  tab[cnt++] = laJungle;
  tab[cnt++] = laCaves;
  
  // the advanced lands
  if(gold() >= 30) {
    tab[cnt++] = laCrossroads;
    tab[cnt++] = laAlchemist;
    tab[cnt++] = laMirror;
    tab[cnt++] = laMotion;
    }
  
  if(gold() >= 60) {
    tab[cnt++] = laCrossroads;
    tab[cnt++] = laRlyeh;
    }
  
  if(tkills() >= 100) {
    tab[cnt++] = laGraveyard;
    }
  
  if(hellUnlocked()) {
    tab[cnt++] = laCrossroads;
    tab[cnt++] = laHell;
    }
  
  if(tkills() >= 2000 && gold() >= 2000)
    tab[cnt++] = laCocytus;
  
  eLand n = old;
  while(n == old) n = tab[rand() % cnt];
  
  return n;  
  }

void setdist(cell *c, int d, cell *from) {
  if(signed(c->mpdist) <= d) return;
  c->mpdist = d;
  
  if(d >= BARLEV) {
    if(!c->land) c->land = from->land;
    }

  if(d == BARLEV) {
    
    if(c->type == 7 && rand() % 10000 < (showoff ? (cwt.c->mpdist > 7 ? 0 : 10000) : c->land == laCrossroads ? 5000 : 50)) {
    
      int bd = 2 + (rand() % 2) * 3;
      
      cellwalker bb(c, bd);
      cellwalker bb2 = bb;

      if(checkBarriersFront(bb) && checkBarriersBack(bb2)) {
        c->bardir = bd;
        eLand oldland = c->land;
        eLand newland = getNewLand(oldland);
        if(showoff) newland = showlist[(showid++) % 10];
        landcount[newland]++;

        if(bd == 5) c->barleft = oldland, c->barright = newland;
        else c->barleft = newland, c->barright = oldland;
        }
      }
    
    if(c->bardir != NODIR) 
      buildBarrier(c);    
    }
  
  if(d < 10) {
    explore[d]++;
    
    if(d < BARLEV)
    for(int i=0; i<c->type; i++) {
      createMov(c, i);
      setdist(c->mov[i], d+1, c);
      }
    
    if(d == 9) {
    
      if(c->land == laIce) if(rand() % 100 < 5 && c->wall != waBarrier) {
        c->wall = waIcewall;
        for(int i=0; i<c->type; i++) if(rand() % 100 < 50) {
          createMov(c, i);
          setdist(c->mov[i], d+1, c);
          cell *c2 = c->mov[i];
          if(c2->wall == waBarrier || c2->land != laIce) continue;
          c2->wall = waIcewall;
          for(int j=0; j<c2->type; j++) if(rand() % 100 < 20) {
            createMov(c2, j);
            setdist(c->mov[i], d+2, c);
            cell *c3 = c2->mov[j];
            if(c3->wall == waBarrier || c3->land != laIce) continue;
            c3->wall = waIcewall;
            }
          }
        }

      if(c->land == laIce || c->land == laCocytus) if(c->wall == waIcewall && items[itDiamond] >= 5 && rand() % 200 == 1)
        c->wall = waBonfire, c->tmp = -1;;

      if(c->land == laCaves) 
        c->wall = rand() % 100 < 55 ? waCavewall : waCavefloor;
      
      if(c->land == laAlchemist) 
        c->wall = rand() % 2 ? waFloorA : waFloorB;
      
      if(c->land == laGraveyard && c->type == 7) 
        c->wall = rand() % 5 ? waAncientGrave : waFreshGrave;
      
      if(c->land == laRlyeh)  {
        if(rand() % 500 < 5) {
          for(int i=0; i<c->type; i++) {
            createMov(c, i);
            setdist(c->mov[i], d+1, c);
            if(c->mov[i] && c->mov[i]->land == laRlyeh)
              c->mov[i]->wall = waColumn;
            }
          
          for(int j=0; j<2; j++) {
            int i = rand() % c->type;
            if(c->mov[i] && c->mov[i]->land == laRlyeh)
              c->mov[i]->wall = waNone;
            }
          }
        if(c->type == 7 && rand() % 2) c->wall = waColumn;
        }
      
      if(c->land == laHell) {
        if(rand() % 100 < 4) {
          for(int i=0; i<c->type; i++) {
            createMov(c, i);
            setdist(c->mov[i], d+1, c);
            if(c->mov[i] && c->mov[i]->land == laHell)
              if(c->mov[i]->wall != waSulphurC)
                c->mov[i]->wall = waSulphur;
            }
          
          c->wall = waSulphurC;
          }
        }
      
      if(c->land == laCocytus)  {
        if(c->wall == waNone) c->wall = waFrozenLake;
        if(rand() % 100 < 5) {
          for(int i=0; i<c->type; i++) {
            createMov(c, i);
            setdist(c->mov[i], d+1, c);
            if(c->mov[i] && c->mov[i]->land == laCocytus)
              c->mov[i]->wall = waLake;
            }
          
          c->wall = waLake;

          if(rand() % 500 < 100 + 2 * (items[itSapphire]))
            c->monst = moShark;
          }
        }
      
      if(c->land == laDesert) {
        if(rand() % 100 < 5) {
          for(int i=0; i<c->type; i++) {
            createMov(c, i);
            setdist(c->mov[i], d+1, c);
            if(c->mov[i] && c->mov[i]->land == laDesert)
              c->mov[i]->wall = waDune;
            }
          
          for(int j=0; j<2; j++) {
            int i = rand() % c->type;
            if(c->mov[i] && c->mov[i]->land == laDesert)
              c->mov[i]->wall = waNone;
            }
          }

        if(rand() % 300 == 1 && items[itSpice] >= 5) 
          c->wall = waThumper, c->tmp = -1;
        }
      }
    
    if(d == 7 && c->wall == waCavewall && rand() % 5000 < items[itGold])
      c->monst = moSeep;
    
    if(d == 7 && c->wall == waChasmD) {
      chasmify(c);
      }
    
    if(d == 7 && passable(c, NULL, false, false) && !safety) {
      int hard = items[itOrbYendor] * 5;
      
      if(c->land == laBarrier) c->wall = waBarrier;
      if(c->land == laIce) {
        if(rand() % 5000 < 100 + 2 * (kills[moYeti] + kills[moWolf]))
          c->item = itDiamond;
        if(rand() % 8000 < 2 * (items[itDiamond] + hard))
          c->monst = rand() % 2 ? moYeti : moWolf;
        }
      if(c->land == laCrossroads) {
        //if(rand() % 9000 < puregold())
        //  c->monst = moRanger;
        if(c->type == 6 && rand() % 8000 < 120 && items[itShard] >= 5)
          c->wall = rand() % 2 ? waMirror : waCloud;
        }
      if(c->land == laCaves) {
        if(rand() % 5000 < 100 + 2 * (kills[moTroll] + kills[moGoblin]))
          c->item = itGold;
        if(rand() % 8000 < 10 + 2 * (items[itGold] + hard))
          c->monst = rand() % 2 ? moTroll : moGoblin;
        }
      if(c->land == laDesert) {
        if(rand() % 5000 < 100 + 2 * (kills[moWorm] + kills[moDesertman]))
          c->item = itSpice;
        if(rand() % 8000 < 10 + 2 * (items[itSpice] + hard))
          c->monst = rand() % 2 ? moWorm : moDesertman,
          c->mondir = NODIR;
        }
      if(c->land == laJungle) {
        if(rand() % 5000 < 25 + 2 * (kills[moIvyRoot] + kills[moMonkey]))
          c->item = itEmerald;
        if(rand() % 15000 < 5 + 1 * (items[itEmerald] + hard))
          c->monst = moMonkey;
        else if(rand() % 80000 < 5 + items[itEmerald] + hard)
          c->monst = moEagle;
        else if(c->type == 7 && rand() % 4000 < 300 + items[itEmerald]) {
          bool hard = rand() % 100 < 25;
          if(hard ? buildIvy(c, 1, 9) : buildIvy(c, 0, 7))
            c->item = itEmerald;
          }
        }
      if(c->land == laAlchemist) {
        if(rand() % 5000 < 25 + min(kills[moSlime], 200))
          c->item = itElixir;
        else if(rand() % 3500 < 10 + items[itElixir] + hard)
          c->monst = moSlime;
        }
      if(c->land == laCrossroads && items[itHell] >= 10) {
        if(rand() % 5000 < tkills())
          c->item = itHyperstone;
        }
      if(c->land == laMirror) {
        if(c->type == 6 && rand() % 5000 < 120)
          c->wall = rand() % 2 ? waMirror : waCloud;
        if(rand() % 12000 < 8 + items[itShard] + hard)
          c->monst = moRanger;
        else if(rand() % 60000 < 8 + items[itShard] + hard)
          c->monst = moEagle;
        }
      if(c->land == laGraveyard) {
        if(rand() % 5000 < 30 + 2 * (kills[moZombie] + kills[moGhost] + kills[moNecromancer]))
          c->item = itBone;
        if(rand() % 20000 < 10 + items[itBone] + hard + (kills[moZombie] + kills[moGhost] + kills[moNecromancer])/60) {
          eMonster grm[6] = { moZombie, moZombie, moZombie, moGhost, moGhost, moNecromancer};
          c->monst = grm[rand() % 6];
          }
        }
      if(c->land == laRlyeh) {
        if(rand() % 5000 < 30 + 2 * (kills[moCultist] + kills[moTentacle] + kills[moPyroCultist]))
          c->item = itStatue;
        if(rand() % 8000 < 5 + items[itStatue] + hard)
          c->monst = moTentacle, c->item = itStatue, c->mondir = NODIR;
        else if(rand() % 12000 < 5 + items[itStatue] + hard)
          c->monst = rand() % 3 ? moCultist : moPyroCultist;
        else if(rand() % 8000 < 5 + items[itStatue] + hard && c->type == 6) {
          for(int t=0; t<c->type; t++) if(c->mov[t] && c->mov[t]->monst == moNone)
            c->mov[t]->wall = c->mov[t]->type == 7 ? waColumn : waNone;
          if(buildIvy(c, 0, 3)) c->item = itStatue;
          }
        }
      if(c->land == laHell) {
        if(rand() % 1500 < 30 + (kills[moCultist] + kills[moTentacle]))
          c->item = itHell;
        if(rand() % 8000 < 40 + items[itHell] + hard)
          c->monst = moLesser;
        else if(rand() % 24000 < 40 + items[itHell] + hard)
          c->monst = moGreater;
        }
      if(c->land == laCocytus) {
        if(rand() % 5000 < 100 + 2 * (kills[moYeti] + kills[moWolf]+kills[moLesser]))
          c->item = itSapphire;
        if(rand() % 5000 < 2 * (items[itSapphire] + hard)) {
          eMonster ms[3] = { moYeti, moWolf, moLesser };
          c->monst = ms[rand() % 3];
          }
        }
      if(c->land == laMotion) {
        if(rand() % 1500 < 30 + (kills[moRunDog]))
          c->item = itFeather;
        if(rand() % 20000 < 25 + items[itFeather] + hard) {
          c->monst = moRunDog;
          chasmify(c);
          }
        }
      if(!c->item) {
        if(orbChance(c, laJungle, 1200, 400) && items[itEmerald] >= 10)
          c->item = itOrbLightning;
        if(orbChance(c, laIce, 2000, 400) && items[itDiamond] >= 10)
          c->item = itOrbFlash;
        if(orbChance(c, laCaves, 1800, 2000) && items[itGold] >= 10)
          c->item = itOrbLife;
        if(orbChance(c, laAlchemist, 500, 400) && items[itElixir] >= 10)
          c->item = itOrbSpeed;
        if(orbChance(c, laGraveyard, 200, 200) && items[itBone] >= 10)
          c->item = itGreenStone;
        if(orbChance(c, laDesert, 2500, 400) && items[itSpice] >= 10)
          c->item = itOrbShield;
        if(orbChance(c, laHell, 2000, 1000) && items[itHell] >= 10)
          c->item = itOrbYendor;
        if(orbChance(c, laRlyeh, 1500, 800) && items[itStatue] >= 10)
          c->item = itOrbTeleport;
        if(orbChance(c, laMotion, 2000, 1000) && items[itFeather] >= 10) {
          c->item = itOrbSafety;
          }

        if(orbChance(c, laIce, 1500, 0) && items[itDiamond] >= 10)
          c->item = itOrbWinter;
        if(orbChance(c, laCocytus, 1500, 0) && items[itSapphire] >= 10)
          c->item = itOrbWinter;
        if(orbChance(c, laCaves, 1200, 0) && items[itGold] >= 10)
          c->item = itOrbDigging;
        }
      }

    }
  }

// find worms and ivies
void settemp(cell *c) {
  temps.push_back(c); tempval.push_back(c->monst); c->monst = moNone;
  }

void findWormIvy(cell *c) {
  while(true) {
    if(c->monst == moWorm || c->monst == moTentacle || c->monst == moWormwait || c->monst == moTentaclewait) {
      worms.push_back(c); settemp(c);
      break;
      }
    else if(c->monst == moWormtail) {
      bool bug = true;
      for(int i=0; i<c->type; i++) {
        cell* c2 = c->mov[i];
        if(c2 && isWorm(c2) && c2->mov[c2->mondir] == c) {
          settemp(c);
          c = c2;
          bug = false;
          }
        }
      if(bug) break;
      }
    else if(c->monst == moIvyWait) {
      cell* c2 = c->mov[c->mondir];
      settemp(c); c=c2;
      }
    else if(c->monst == moIvyHead) {
      ivies.push_back(c); settemp(c);
      break;
      }
    else if(c->monst == moIvyBranch || c->monst == moIvyRoot) {
      bool bug = true;
      for(int i=0; i<c->type; i++) {
        cell* c2 = c->mov[i];
        if(c2 && (c2->monst == moIvyHead || c2->monst == moIvyBranch) && c2->mov[c2->mondir] == c) {
          settemp(c);
          c = c2;
          bug = false;
          }
        }
      if(bug) break;
      }
    else break;
    }
  }

// calculate cpdist and pathdist
void bfs() {
  
  int dcs = size(dcal);
  for(int i=0; i<dcs; i++) dcal[i]->cpdist = INFD;
  dcal.clear(); 
  worms.clear(); ivies.clear(); ghosts.clear(); golems.clear(); mirrors.clear();
  temps.clear(); tempval.clear();
  
  int pqs = size(pathq);
  for(int i=0; i<pqs; i++) pathq[i]->pathdist = INFD;
  pathq.clear(); pathqm.clear();
  pathq.push_back(cwt.c);
  cwt.c->pathdist = 0;

  dcal.push_back(cwt.c);
  
  cwt.c->cpdist = 0;
  int qb = 0;
  while(true) {
    cell *c = dcal[qb++];
    int d = c->cpdist;
    if(d == 7) { first7 = qb; break; }
    for(int i=0; i<c->type; i++) if(c->mov[i]) {
      // printf("i=%d cd=%d\n", i, c->mov[i]->cpdist);
      cell *c2 = c->mov[i];
      if(c2 && signed(c2->cpdist) > d+1) {
        c2->cpdist = d+1;
        c2->ligon = 0;
        dcal.push_back(c2);
        if(c2->monst) {
          if(isWorm(c2) || isIvy(c2)) findWormIvy(c2);
          else if(isMimic(c2)) mirrors.push_back(c2);
          else if(c2->monst == moGhost) ghosts.push_back(c2);
          else if(c2->monst == moGolem) golems.push_back(c2);
          }
        if(c2->wall == waThumper && c2->tmp > 0) {
          useup(c2);
          c2->pathdist = 1; pathq.push_back(c2);
          }
        }
      }
    }

  int qtemp = size(temps);
  for(int i=0; i<qtemp; i++) temps[i]->monst = tempval[i];
  
  qb = 0;
  while(qb < size(pathq)) {
    cell *c = pathq[qb++];
    if(c->monst) { 
      pathqm.push_back(c); 
      continue; // no paths going through monsters
      }
    if(c->cpdist > 7) continue;
    int d = c->pathdist;
    for(int i=0; i<c->type; i++) {
      // printf("i=%d cd=%d\n", i, c->mov[i]->cpdist);
      if(c->mov[i] && c->mov[i]->pathdist == INFD && 
        (c->mov[i]->monst || passable(c->mov[i], d==0?NULL:c, true, false))) {
        c->mov[i]->pathdist = d+1;
        pathq.push_back(c->mov[i]);
        }
      }
    }
  }

// initialize the game
void initgame() {
  cwt.c = origin.c7; cwt.spin = 0;
  cwt.c->land = firstland;
  
  // extern int sightrange; sightrange = 9;
  // cwt.c->land = laHell; items[itHell] = 10;
  for(int i=9; i>=0; i--) {
    setdist(cwt.c, i, NULL);
    verifycells(&origin);
    }
  cwt.c->wall = waNone;
  cwt.c->item = itNone;
  cwt.c->monst = moNone;
  
  if(!safety) {
    timerstart = time(NULL); turncount = 0;  
    cheater = 0;
    if(firstland != laIce) cheater++;
    }
  
  // items[itGreenStone] = 100;
  // items[itOrbTeleport] = 100;
  
  /*
  items[itGold] = 20;
  items[itDiamond] = 20;
  items[itSpice] = 20;
  items[itEmerald] = 20;
  items[itElixir] = 20;
  */
  
  /*
  items[itOrbShield]    = 100;
  items[itOrbSpeed]     = 100;
  items[itOrbWinter]    = 100;
  items[itOrbLightning] = 100;
  */
  
  // items[itOrbLightning]    = 100;
  // items[itEmerald]      = 100;
  
  bfs();
  }

void moveNormal(cell *c) {
  bool repeat = true;
  eMonster m = c->monst;
  int nc = 0;
  
  again:

  for(int j=0; j<c->type; j++) 
    if(c->mov[j] && isFriendly(c->mov[j]) && repeat) {
      addMessage(s0 + "The " + minf[m].name + " destroys the " + minf[c->mov[j]->monst].name + "!");
      c->mov[j]->monst = moNone;
      return;
      }

  cell *posdest[10];
  
  for(int j=0; j<c->type; j++)
    if(c->mov[j] && c->mov[j]->pathdist < c->pathdist && passable(c->mov[j], c, false, false)) 
//    if(m == moNecromancer ? c->mov[j]->land == laGraveyard : true)
//    it is more fun when demons step into the Land of Eternal Motion, IMO
//    if((m == moLesser || m == moGreater) ? c->mov[j]->land != laMotion : true)
      if(c->mov[j]->cpdist > 0)
        posdest[nc++] = c->mov[j];
      
/*
  // Eagles can fly through chasms/slime if needed
  if(m == moEagle && nc == 0) {
    printf("no eagle no fly\n");
    for(int j=0; j<c->type; j++) printf("%d from %d\n", c->mov[j]->cpdist, c->cpdist);
    for(int j=0; j<c->type; j++)
      if(c->mov[j] && c->mov[j]->cpdist < c->cpdist && 
        !c->mov[j]->monst && 
        (c->mov[j]->land == laMotion || c->mov[j]->land == laAlchemist) && c->wall != waDeadTroll)
        if(c->mov[j]->cpdist > 0)
          posdest[nc++] = c->mov[j];
    printf("nc=%d\n", nc);
    } */

  if(!nc) return;  
  nc = rand() % nc;
  cell *c2 = posdest[nc];
  c2->monst = m, c->monst = moNone;
  if(m == moEagle && repeat && c->pathdist > 1) { repeat = false; c = c2; goto again; }
  }

void moveWorm(cell *c) {

  if(c->monst == moWormwait) { c->monst = moWorm; return; }
  else if(c->monst == moTentaclewait) { c->monst = moTentacle; return; }
  else if(c->monst != moWorm && c->monst != moTentacle) return;

  int ncg = 0, ncb = 0;
  cell *gmov[7], *bmov[7];
  
  int id = c->monst - moWorm;
  
  for(int j=0; j<c->type; j++) {
    if(c->mov[j] && isFriendly(c->mov[j])) {
      addMessage(s0 + "The sandworm eats the " + minf[c->mov[j]->monst].name + "!");
      ncg = 1; gmov[0] = c->mov[j];
      break;
      }
    if(c->mov[j] && passable(c->mov[j], c, false, false) && c->mov[j]->land != laMotion && c->mov[j] != cwt.c) {
      if(c->mov[j]->pathdist < c->pathdist) gmov[ncg++] = c->mov[j]; else bmov[ncb++] = c->mov[j];
      }
    }

  if(ncg == 0 && ncb == 0) {
    int spices = 0;
    if(id) {
      addMessage("Cthulhu withdraws his tentacle!");
      kills[moTentacle]++;
      }
    else {
      addMessage("The sandworm explodes in a cloud of Spice!");
      kills[moWorm]++;
      spices = 3;
      }
    while(c->monst == moWorm || c->monst == moWormtail || c->monst == moTentacle || c->monst == moTentacletail) {

      for(int j=0; j<c->type; j++) {
        cell* c2 = c->mov[j];
        if(c2 && !id) {
          c2->heat += 0.5;
          if((c2->wall == waDune || c2->wall == waIcewall ||
            c2->wall == waAncientGrave || c2->wall == waFreshGrave || 
            c2->wall == waColumn || c2->wall == waThumper || c2->wall == waBonfire))
            c2->wall = waNone;
          if(c2->wall == waCavewall || c2->wall == waDeadTroll) c2->wall = waCavefloor;
          }
        }

      if(spices > 0 && c->land == laDesert) c->item = itSpice, spices--;
      c->monst = moNone;
      if(c->mondir != NODIR) c = c->mov[c->mondir];
      }
    return;
    }
  
  cell* goal;
  if(ncg) goal = gmov[rand() % ncg];
  else goal = bmov[rand() % ncb];
  
  for(int j=0; j<c->type; j++) if(c->mov[j] == goal) {
    goal->monst = eMonster(moWormwait + id);
      
    c->monst = eMonster(moWormtail + id);
    goal->mondir = c->spn[j];
    
    if(id) break;
    
    cell *c2 = c, *c3 = c2;
    for(int a=0; a<15; a++)
      if(c2->monst == moWormtail) {
        if(c2->mondir == NODIR) return;
        c3 = c2, c2 = c3->mov[c2->mondir];
        }
    
    if(c2->monst == moWormtail) c2->monst = moNone, c3->mondir = NODIR;
    }

  }

void ivynext(cell *c) {
  cellwalker cw(c, c->mondir);
  cw.c->monst = moIvyWait;
  bool findleaf = false;
  while(true) {
    cwspin(cw, 1);
    if(cw.spin == signed(cw.c->mondir)) {
      if(findleaf) { 
        cw.c->monst = moIvyHead; break;
        }
      cw.c->monst = moIvyWait;
      cwstep(cw);
      continue;
      }
    cwstep(cw);
    if(cw.c->monst == moIvyWait && signed(cw.c->mondir) == cw.spin) {
      cw.c->monst = moIvyBranch;
      findleaf = true; continue;
      }
    cwstep(cw);
    }
  }

void moveivy() {
  for(int i=0; i<size(ivies); i++) {
    cell *c = ivies[i];
    cell *co = c;
    if(c->monst != moIvyHead) continue;
    ivynext(c);

    cell *mto = NULL;
    int pd = c->pathdist;
    int sp = 0;
    
    while(c->monst != moIvyRoot) {
      for(int j=0; j<c->type; j++) {
        if(c->mov[j] && isFriendly(c->mov[j])) {
          addMessage(s0 + "The ivy destroys the " + minf[c->mov[j]->monst].name + "!");
          c->mov[j]->monst = moNone;
          continue;
          }
        if(c->mov[j] && signed(c->mov[j]->pathdist) < pd && passable(c->mov[j], c, false, false))
          mto = c->mov[j], pd = mto->pathdist, sp = c->spn[j];
        }
      c = c->mov[c->mondir];
      }

    if(mto && mto->cpdist) {        
      mto->monst = moIvyWait, mto->mondir = sp;
      // if this is the only branch, we want to move the head immediately to mto instead
      if(mto->mov[mto->mondir]->monst == moIvyHead) {
        mto->monst = moIvyHead; co->monst = moIvyBranch;
        }
      }
    else if(co->mov[co->mondir]->monst != moIvyRoot) {
      // shrink useless branches, but do not remove them completely (at the root)
      if(co->monst == moIvyHead) co->mov[co->mondir]->monst = moIvyHead;
      co->monst = moNone;
      }
    }
  }

// move slimes, and also seeps

int sval = 1;
vector<cell*> slimedfs;

void slimevisit(cell *c, cell *from) {
  if(!c) return;
  if(c->tmp == sval) return;
  if(c->land != laAlchemist && c->wall != waCavewall && c->wall != waLake) return;
  if(c->wall != from->wall && from != cwt.c) return;
  if(c->item) return;
  if(c->wall == waThumper || c->wall == waBonfire) return;
  c->tmp = sval;
  if(size(slimedfs) < 1000) slimedfs.push_back(c);
  if(!isWorm(c) && !isIvy(c))
    for(int i=0; i<c->type; i++) if(c->mov[i] == from)
      c->mondir = i;
  if(c->monst == moSlime || c->monst == moSeep || c->monst == moShark || c->monst == moGreaterShark) {

    for(int j=0; j<c->type; j++) 
      if(c->mov[j] && isFriendly(c->mov[j])) {
        addMessage(s0 + "The " + minf[c->monst].name + " eats the " + minf[c->mov[j]->monst].name + "!");
        c->mov[j]->monst = moNone;
        return;
        }
    
    if(from->cpdist == 0 || from->monst) return;

    from->monst = c->monst, c->monst = moNone;
    }
  }

void moveslimes() {
  sval++;
  slimedfs.clear();
  slimedfs.push_back(cwt.c);
  for(int i=0; i<size(slimedfs); i++) {
    cell *c = slimedfs[i];
    for(int t=0; t<c->type; t++)
      slimevisit(c->mov[t], c);
    }
  }

// move eagles

vector<cell*> eagledfs;

void eaglevisit(cell *c, cell *from, int id) {
  if(!c) return;
  if(c->tmp == sval) return;
  if(c->wall == waThumper || c->wall == waBonfire) return;
  c->tmp = sval;
  if(c->monst == moEagle) {
    if(id == 1) for(int j=0; j<c->type; j++) 
      if(c->mov[j] && isFriendly(c->mov[j])) {
        addMessage(s0 + "The " + minf[c->monst].name + " claws the " + minf[c->mov[j]->monst].name + "!");
        c->mov[j]->monst = moNone;
        return;
        }
    
    if(from->cpdist == 0 || from->monst) return;

    from->monst = c->monst, c->monst = moNone;
    }
  if(!eaglepassable(c)) return;
  if(size(eagledfs) < 1000) eagledfs.push_back(c);
  }

void moveeagles(int id) {
  sval++;
  eagledfs.clear();
  eagledfs.push_back(cwt.c);
  for(int i=0; i<size(eagledfs); i++) {
    cell *c = eagledfs[i];
    for(int t=0; t<c->type; t++)
      eaglevisit(c->mov[t], c, id);
    }
  }

#define SHSIZE 16

cell *shpos[SHSIZE];
int cshpos = 0;

void clearshadow() {
  for(int i=0; i<SHSIZE; i++) shpos[i] = NULL;
  }

void moveshadow() {
  if(shpos[cshpos] && shpos[cshpos]->monst == moShadow)
    shpos[cshpos]->monst = moNone;
  shpos[cshpos] = cwt.c;
  cshpos = (cshpos+1) % SHSIZE;
  if(shpos[cshpos] && shpos[cshpos]->monst == moNone && shpos[cshpos]->cpdist && shpos[cshpos]->land == laGraveyard)
    shpos[cshpos]->monst = moShadow;
  }

void moveghosts() {
  for(int i=0; i<size(ghosts); i++) {
    cell *c = ghosts[i];
    if(c->monst == moGhost && c->cpdist > 1) {
      cell *mpos[7];

      for(int j=0; j<c->type; j++) 
        if(c->mov[j] && isFriendly(c->mov[j])) {
          addMessage(s0 + "The " + minf[c->monst].name + " scares the " + minf[c->mov[j]->monst].name + "!");
          c->mov[j]->monst = moNone;
          return;
          }
    
      int qmpos = 0;
      for(int k=0; k<c->type; k++) if(c->mov[k] && !c->mov[k]->monst && c->mov[k]->cpdist < c->cpdist)
        mpos[qmpos++] = c->mov[k];
      if(!qmpos) continue;
      cell *c2 = mpos[rand() % qmpos];
      c2->monst = moGhost; c->monst = moNone;
      }
    }
  }

void movegolems() {
  for(int i=0; i<size(golems); i++) {
    cell *c = golems[i];
    if(c->monst == moGolem) {
      int bestv = 100, bq = 0, bdirs[7];
      for(int k=0; k<c->type; k++) if(c->mov[k]) {
        int val;
        if(c->mov[k] == cwt.c) val = 0;
        else if(isActiveEnemy(c->mov[k], NULL) && isKillable(c->mov[k]))
          val = 12000;
        else if(isInactiveEnemy(c->mov[k]) && isKillable(c->mov[k]))
          val = 10000;
        else if(isIvy(c->mov[k])) val = 8000;
        else if(monstersnear(c->mov[k], NULL, false)) val = 0;
        else if(passable(c->mov[k], c, false, false)) val = 4000;
        else val = 0;
        val -= c->mov[k]->pathdist;
        if(val > bestv) bestv = val, bq = 0;
        if(val == bestv) bdirs[bq++] = k;
        }
      if(bestv <= 100) continue;
      int dir = bdirs[rand() % bq];
      cell *c2 = c->mov[dir];
      if(c2->monst) {
        addMessage(s0 + "The golem destroys the " + minf[c2->monst].name + "!");
        killMonster(c2);
        }
      else {
        c2->monst = moGolemMoved;
        c->monst = moNone;
        }
      }
    }
  }

bool wchance(int a, int of) {
  of *= 10; 
  a += items[itOrbYendor] * 5 + 1;
  if(cwt.c->land == laCrossroads) a+= items[itHyperstone] * 10;
  for(int i=0; i<firstnontreasure; i++) 
    a = max(a, (items[i]-10) / 10);
  return rand() % (a+of) < a;
  }

void wandering() {
  while(first7 < size(dcal)) {
    int i = first7 + rand() % (size(dcal) - first7);
    cell *c = dcal[i];
    if(c->monst || c->pathdist == INFD) break;

    else if(c->land == laIce && wchance(items[itDiamond], 10))
      c->monst = rand() % 2 ? moWolf : moYeti;

    else if(c->land == laDesert && wchance(items[itSpice], 10))
      c->monst = rand() % 10 ? moDesertman : moWorm;

    else if(c->land == laCaves && wchance(items[itGold], 5))
      c->monst = rand() % 3 ? moTroll : moGoblin;

    else if(c->land == laJungle && wchance(items[itEmerald], 40))
      c->monst = rand() % 10 ? moMonkey : moEagle;

    else if(c->land == laMirror && wchance(items[itShard], 15))
      c->monst = rand() % 10 ? moRanger : moEagle;

    else if(c->land == laHell && wchance(items[itHell], 20))
      c->monst = rand() % 3 ? moLesser : moGreater;

    else if(c->land == laRlyeh && wchance(items[itStatue], 15))
      c->monst = rand() % 3 ? moPyroCultist : moCultist;

    else if(c->land == laGraveyard && wchance(items[itBone], 15))
      c->monst = rand() % 5 ? moGhost : moNecromancer;
      
    else if(c->land == laAlchemist && wchance(items[itElixir], 3) && c->tmp == sval)
      c->monst = moSlime;
    
    else break;
      
    if(c->monst == moWorm) c->mondir = NODIR;
      
    // laMotion, laCrossroads -> no respawn!
    }
  }

void movemonsters() {
  
  DEBT("golems");
  movegolems();
  DEBT("ghosts");
  moveghosts();
  
  DEBT("normal");
  for(int i=0; i<size(pathqm); i++) {
    cell *c = pathqm[i];
    eMonster m = c->monst;
    
    if(isActiveEnemy(c, NULL)) {
      if(c->pathdist == 1 && c->monst != moGhost) {
        // c->iswall = true; c->ismon = false;
        if(items[itOrbShield]) continue;

        addMessage(s0 + "The " + minf[m].name + " is confused!");
        // playerdead = true;
        break;
        }
      
      if(c->monst == moNecromancer) {
        int gravenum = 0, zombienum = 0;
        cell *gtab[8], *ztab[8];
        for(int j=0; j<c->type; j++) if(c->mov[j]) {
          if(c->mov[j]->wall == waFreshGrave) gtab[gravenum++] = c->mov[j];
          if(passable(c->mov[j], c, false, false) && c->mov[j]->pathdist < c->pathdist)
            ztab[zombienum++] = c->mov[j];
          }
        if(gravenum && zombienum) {
          cell *gr = gtab[rand() % gravenum];
          gr->wall = waAncientGrave;
          gr->monst = moGhost;
          ztab[rand() % zombienum]->monst = moZombie;
          addMessage("The Necromancer raises some undead!");
          continue;
          }
        }
      
      if(c->monst == moWolf) {
        int bhd = NODIR;
        ld besth = c->heat;
        for(int j=0; j<c->type; j++) if(c->mov[j]->heat > besth && passable(c->mov[j], c, false, false))
          besth = c->mov[j]->heat, bhd = j;
        if(bhd != NODIR) {
          // printf("wolf moved from %Lf (%p) to %Lf (%p)\n", c->heat, c, besth, c->mov[bhd]);
          c->mov[bhd]->monst = moWolfMoved, c->monst = moNone;
          }
        }
      
      else if(c->monst == moPyroCultist && c->cpdist <= 4 && cwt.c->wall == waNone) {
        addMessage(s0+"The "+(minf[c->monst].name)+" throws fire at you!");
        cwt.c->wall = waBonfire;
        cwt.c->tmp = 20;
        c->monst = moCultist;
        }
      
      else if(m == moYeti || m == moRanger || m == moGoblin || m == moTroll || m == moDesertman || m == moMonkey ||
        m == moZombie || m == moNecromancer || m == moCultist || m == moLesser || m == moGreater ||
        m == moRunDog || m == moPyroCultist)
        moveNormal(c);
      }
    }

  int dcs = size(dcal);

  DEBT("worm");
  int wrm = size(worms);
  for(int i=0; i<wrm; i++) {
    moveWorm(worms[i]);
    }

  DEBT("ivy");
  moveivy();
  DEBT("slimes");
  moveslimes();
  DEBT("eagles");
  moveeagles(1);
  moveeagles(2);
  
  DEBT("fresh");
  for(int i=0; i<dcs; i++) {
    cell *c = dcal[i];
    
    if(c->monst == moWolfMoved) c->monst = moWolf;
    if(c->monst == moIvyNext) {
      c->monst = moIvyHead; ivynext(c);
      }
    if(c->monst == moIvyDead) c->monst = moNone;
    if(c->monst == moGolemMoved) c->monst = moGolem;
    if(c->monst == moLesser) c->monst = moLesserM;
    else if(c->monst == moLesserM) c->monst = moLesser;
    if(c->monst == moGreater) c->monst = moGreaterM;
    else if(c->monst == moGreaterM) c->monst = moGreater;
    
    if(c->wall == waChasm) {
      c->item = itNone;
      if(c->monst && c->monst != moGhost && c->monst != moEagle) killMonster(c);
      }

    if(c->wall == waLake) {
      c->item = itNone;
      if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
        c->monst = moGreaterShark;
      if(c->monst && c->monst != moShark && c->monst != moGreaterShark) killMonster(c);
      }

    if(c->monst && c->land == laMotion && c->monst != moGhost && c->monst != moEagle) {
      c->wall = waChasm;
      }
    }

  DEBT("shadow");
  moveshadow();
  
  DEBT("wandering");
  wandering();
  }

// move heat
void heat() {
  int oldmelt = kills[0];

  /* if(cwt.c->heat > .5)  cwt.c->heat += .3;
  if(cwt.c->heat > 1.)  cwt.c->heat += .3;
  if(cwt.c->heat > 1.4) cwt.c->heat += .5; */
  cwt.c->heat += 1.2;
  int dcs = size(dcal);
  for(int i=0; i<dcs; i++) {
    cell *c = dcal[i];
    if(c->cpdist > 8) break;
    if(c->wall == waBonfire && c->tmp > 0) useup(c);
    if(!isIcyLand(c)) continue;
    if(c->monst == moRanger) c->heat += 3;
    if(c->monst == moDesertman) c->heat += 4;
    if(c->monst == moMonkey) c->heat ++;
    if(c->wall == waDeadTroll) c->heat -= 2;
    if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
      c->heat += (c->land == laCocytus ? 1.5 : 10);
    if(c->monst == moGreaterShark)
      c->heat += 3;
    if(c->monst == moCultist) c->heat += 3;
    if(c->monst == moPyroCultist) c->heat += 6;
    if(c->wall == waBonfire && c->tmp > 0) c->heat += 4;
    
    ld hmod = 0;
    
    for(int j=0; j<c->type; j++) if(c->mov[j]) {
      if(!isIcyLand(c->mov[j])) {
        if(c->mov[j] == cwt.c) hmod += 1.2;
        continue;
        }
      ld hdiff = c->mov[j]->heat - c->heat;
      hdiff /= 10;
      if(c->mov[j]->cpdist <= 8)
        c->mov[j]->heat -= hdiff;
      else
        hdiff = -c->heat / 250;
      hmod += hdiff;
      }
    
    c->heat += hmod;
    }
  
  for(int i=0; i<dcs; i++) {
    cell *c = dcal[i];
    if(c->wall == waIcewall && c->heat > .4) c->wall = waNone, kills[0]++;
    if(c->wall == waFrozenLake && c->heat > .8) c->wall = waLake, kills[0]++;
    }

  if(kills[0] != oldmelt) bfs();
  }

void livecaves() {
  int dcs = size(dcal);
  for(int i=0; i<dcs; i++) {
    cell *c = dcal[i];
    if(c->cpdist > 8) break;
    if(c->land != laCaves) continue;
    
    if(c->wall == waThumper || c->wall == waBonfire) continue;
    c->tmp = 0;
    if(c->item || c->monst || c->cpdist == 0 || c->wall == waDeadTroll) continue;
    for(int j=0; j<c->type; j++) if(c->mov[j]) {
      if(c->mov[j]->wall == waCavefloor) c->tmp++;
      else if(c->mov[j]->wall == waCavewall) c->tmp--;
      else if(c->mov[j]->wall == waDeadTroll) c->tmp -= 5;
      else if(c->mov[j]->wall != waBarrier) c->tmp += 5;      
      if(c->mov[j]->cpdist == 0 && items[itOrbDigging]) c->tmp+=100;
      if(c->mov[j]->wall == waThumper && c->mov[j]->tmp > 0) c->tmp+=100;
      if(c->mov[j]->wall == waBonfire) c->tmp+=100;
      if(c->mov[j]->item) c->tmp+=2;
      if(c->mov[j]->monst == moZombie) c->tmp += 10;
      if(c->mov[j]->monst == moGhost) c->tmp += 10;
      if(c->mov[j]->monst == moNecromancer) c->tmp += 10;
      if(c->mov[j]->monst == moWormtail) c->tmp++;
      if(c->mov[j]->monst == moTentacletail) c->tmp-=2;
      if(isIvy(c->mov[j])) c->tmp--;
      if(isDemon(c->mov[j])) c->tmp-=3;
      // if(c->mov[j]->monst) c->tmp++;
      // if(c->mov[j]->monst == moTroll) c->tmp -= 3;
      }
    }

  for(int i=0; i<dcs; i++) {
    cell *c = dcal[i];
    if(c->cpdist > 8) break;
    if(c->land != laCaves) continue;
    if(c->wall == waThumper || c->wall == waBonfire) continue;
    
    if(c->tmp > 0) c->wall = waCavefloor;
    if(c->tmp < 0) c->wall = waCavewall;
    }
  
  }

// mirror management

void createMirrors(cell *c, int dir, eMonster type) {
  cellwalker C(c, dir);
  
  if(type == moMirror) type = moMimic;
  else type = moMirror;

  for(int i=0; i<6; i++) {
    cwstep(C);
    if(C.c->type == 6) {
      cwspin(C, i);
      if(C.c->monst == moNone && C.c->wall == waNone) {
        C.c->monst = type;
        C.c->mondir = C.spin;
        }
      cwspin(C, -i);
      }
    cwstep(C);
    cwspin(C, 1);
    }
  }

void createMimics(cell *c, int dir, eMonster type) {
  cellwalker C(c, dir);
  for(int i=0; i<6; i++) {
    cwstep(C);
    if(C.c->type == 6) {
      cwspin(C, 2);
      cwstep(C);
      cwspin(C, 4-i);
      if(C.c->monst == moNone && C.c->wall == waNone) {
        C.c->monst = type;
        C.c->mondir = C.spin;
        }
      cwspin(C, 6-4+i);
      cwstep(C);
      cwspin(C, 2);
      cwstep(C);
      cwspin(C, 2-i);
      if(C.c->monst == moNone && C.c->wall == waNone) {
        C.c->monst = type;
        C.c->mondir = C.spin;
        }
      cwspin(C, 6-2+i);
      cwstep(C);
      cwspin(C, 2);
      }
    cwstep(C);
    cwspin(C, 1);
    }
  }

void spinmirrors(int d) {

  for(int i=0; i<size(mirrors); i++) {
    cell *c = mirrors[i];
    if(c->monst == moMirror) 
      mirrors[i]->mondir = (mirrors[i]->mondir - d + 42) % mirrors[i]->type;
    if(c->monst == moMimic)
      mirrors[i]->mondir = (mirrors[i]->mondir + d + 42) % mirrors[i]->type;
    }

  }

void gomirrors(bool go) {
  for(int i=0; i<size(mirrors); i++) {
    cell *c = mirrors[i];
    eMonster m = c->monst;
    if(m == moMirror || m == moMimic) {
      cell *c2 = c->mov[c->mondir];
      if(c2 && c2->monst != moNone && !isMimic(c2) && isKillable(c2)) {
        addMessage(s0 + "The " + minf[m].name + " destroys the " + minf[c2->monst].name + "!");
        killMonster(c2);
        }
      if(!go) continue;
      c->monst = moNone;
      if(!c2) continue;
      if(!passable(c2, c, true, true))  continue;
      if(isWorm(c2)) continue;
      if(c2->monst == moGreater) {
        c2->monst = moLesser; continue;
        }
      if(c2->monst == moGreaterM) {
        c2->monst = moLesserM; continue;
        }
      if(c2 == cwt.c) {
        addMessage(s0 + "You join the " + minf[m].name + ".");
        continue;
        }
      if(isMimic(c2)) {
        addMessage("Two of your images crash and disappear!");
        c2->monst = moNone;
        continue;
        }
      if(isIvy(c2)) killIvy(c2);
      c2->monst = m;
      c2->mondir = c->spn[c->mondir];
      if(c2->wall == waMirror) {
        addMessage(s0 + "The " + minf[m].name + " breaks the mirror!");
        createMirrors(c2, c2->mondir, m);
        c2->wall = waNone;
        }
      if(c2->wall == waCloud) {
        addMessage(s0 + "The " + minf[m].name + " disperses the cloud!");
        createMimics(c2, c2->mondir, m);
        c2->wall = waNone;
        }
      }
    }
  }

void reduceOrbPowers() {
  if(items[itOrbLightning]) items[itOrbLightning]--;
  if(items[itOrbSpeed]) items[itOrbSpeed]--;
  if(items[itOrbFlash]) items[itOrbFlash]--;
  if(items[itOrbShield]) items[itOrbShield]--;
  if(items[itOrbWinter]) items[itOrbWinter]--;
  if(items[itOrbDigging]) items[itOrbDigging]--;
  if(items[itOrbTeleport]) items[itOrbTeleport]--;
  }

void flashAlchemist(cell *c) {
  if(c->wall == waFloorA || c->wall == waFloorB) {
    if(cwt.c->wall == waFloorA || cwt.c->wall == waFloorB)
      c->wall = cwt.c->wall;
    else
      c->wall = eWall(c->wall ^ waFloorB ^ waFloorA);
    }
  }

void activateFlash() {
  extern void drawFlash();
  drawFlash();
  addMessage("You activate the Flash spell!");
  items[itOrbFlash] = 0;
  for(int i=0; i<size(dcal); i++) {
    cell *c = dcal[i];
    if(c->cpdist > 2) break;
    flashAlchemist(c);
    killMonster(c);
    c->heat += 2;
    if(c->wall == waCavewall)  c->wall = waCavefloor;
    if(c->wall == waDeadTroll) c->wall = waCavefloor;
    if(c->wall == waMirror)    c->wall = waNone;
    if(c->wall == waCloud)     c->wall = waNone;
    if(c->wall == waDune)      c->wall = waNone;
    if(c->wall == waAncientGrave) c->wall = waNone;
    if(c->wall == waFreshGrave) c->wall = waNone;
    if(c->wall == waColumn)    c->wall = waNone;
    if(isActiv(c))             c->tmp = 77;
    }
  }

bool barrierAt(cellwalker& c, int d) {
  if(d >= 7) return true;
  if(d <= -7) return true;
  d = c.spin + d + 42;
  d%=c.c->type;
  if(!c.c->mov[d]) return true;
  if(c.c->mov[d]->wall == waBarrier) return true;
  return false;
  }

void activateLightning() {
  extern void drawLightning();
  drawLightning();
  addMessage("You activate the Lightning spell!");
  items[itOrbLightning] = 0;
  for(int i=0; i<cwt.c->type; i++) {
    cellwalker lig(cwt.c, i);
    int bnc = 0;
    while(true) {
      // printf("at: %p i=%d d=%d\n", lig.c, i, lig.spin);
      if(lig.c->mov[lig.spin] == 0) break;
      cwstep(lig);
      
      cell *c = lig.c;

      flashAlchemist(c);
      killMonster(c);
      c->heat += 2;
      c->ligon = 1;
      
      bool brk = false, spin = false;
      if(c->wall == waCavewall)  c->wall = waCavefloor, brk = true;
      if(c->wall == waDeadTroll) c->wall = waCavefloor, brk = true;
      if(c->wall == waMirror)    c->wall = waNone;
      if(c->wall == waCloud)     c->wall = waNone;
      if(c->wall == waDune)      c->wall = waNone, brk = true;
      if(c->wall == waIcewall)   c->wall = waNone, brk = true;
      if(c->wall == waAncientGrave) c->wall = waNone, spin = true;
      if(c->wall == waFreshGrave) c->wall = waNone, spin = true;
      if(c->wall == waColumn)    c->wall = waNone, spin = true;
      if(c == cwt.c)             brk = true;
      if(isActiv(c))             c->tmp = 77;
      if(spin) cwspin(lig, rand() % lig.c->type);
      
      if(brk) break;
      
      if(c->wall == waBarrier)   {
        int left = -1;
        int right = 1;
        while(barrierAt(lig, left)) left--;
        while(barrierAt(lig, right)) right++;
        cwspin(lig, -(right + left));
        bnc++; if(bnc > 10) break;
        }
      else {
        cwspin(lig, 3);
        if(c->type == 7) cwspin(lig, rand() % 2);
        }
      }
    }
  }

// move the PC in direction d (or stay in place for d == -1)

bool canmove = true;

bool checkNeedMove(bool checkonly) {
  if(cwt.c->wall == waLake) {
    if(checkonly) return true;
    addMessage(s0 + "Ice below you is melting! RUN!");
    }
  else if(cwt.c->wall == waBonfire && items[itOrbShield] < 2) {
    if(checkonly) return true;
    addMessage(s0 + "This spot will be burning soon! RUN!");
    }
  else if(cwt.c->wall == waChasm) {
    if(checkonly) return true;
    addMessage(s0 + "The floor has collapsed! RUN!");
    }
  else return false;
  return true;
  }

#define YDIST 101

struct yendorinfo {
  cell *path[YDIST];
  bool found;
  };

vector<yendorinfo> yi;

int yii = 0;

bool checkYendor(cell *yendor, bool checkonly) {
  int byi = size(yi);
  for(int i=0; i<size(yi); i++) if(yi[i].path[0] == yendor) byi = i;
  if(byi < size(yi) && yi[byi].found) return true;
  if(checkonly) return false;
  if(byi == size(yi)) {
    yendorinfo nyi;
    nyi.path[0] = yendor;

    cellwalker lig(yendor, rand() % yendor->type);

    cell *prev = yendor;
    
    for(int i=0; i<YDIST-1; i++) {
      nyi.path[i] = lig.c;
      
      prev = lig.c;
      cwstep(lig);
      cwspin(lig, 3);
      if(lig.c->type == 7) cwspin(lig, rand() % 2);
            
      setdist(lig.c, 10, prev);
      setdist(lig.c, 9, prev);
      }

    nyi.path[YDIST-1] = lig.c;
    nyi.found = false;

    cell *key = lig.c;

    for(int b=10; b>=7; b--) setdist(key, b, prev);
    
    for(int i=-1; i<key->type; i++) {
      cell *c2 = i >= 0 ? key->mov[i] : key;
      c2->monst = moNone; c2->item = itNone;
      if(!passable(c2, NULL, true, true)) {
        if(c2->wall == waCavewall) c2->wall = waCavefloor;
        else if(c2->wall == waLake) c2->wall = waFrozenLake;
        else c2->wall = waNone;
        }
      }
    key->item = itKey;

    yi.push_back(nyi);
    }
  yii = byi;
  addMessage("You need to find the right Key to unlock this Orb of Yendor!");
  return false;
  }

void activateSafety(eLand l) {
  extern void drawSafety();
  drawSafety();
  addMessage("You fall into a wormhole!");
  eLand f = firstland;
  firstland = l;
  safety = true;
  clearMemory();
  initcells();
  initgame();
  firstland = f;
  safety = false;
  extern void restartGraph();
  restartGraph();  
  }

bool hasSafeOrb(cell *c) {
  return 
    c->item == itOrbSafety ||
    c->item == itOrbShield ||
    c->item == itOrbYendor;
  }


bool movepcto(int d, bool checkonly = false) {
  if(checkonly && items[itOrbTeleport]) return true;
  if(!checkonly) flipplayer = false;
  if(!checkonly) DEB("movepc");
  if(d >= 0) {
    cwspin(cwt, d);
    spinmirrors(d);
    d = cwt.spin;
    }
  playermoved = true;
  if(d >= 0) {
    cell *c2 = cwt.c->mov[d];
    
    if(!passable(c2, cwt.c, false, true) && items[itOrbFlash]) {
      if(checkonly) return true;
      activateFlash();
      return true;
      }

    if(!passable(c2, cwt.c, false, true) && items[itOrbLightning]) {
      if(checkonly) return true;
      activateLightning();
      return true;
      }

    if(isActiv(c2) && c2->tmp == -1) {
      if(checkonly) return true;
      addMessage(s0 + "You activate the " + winf[c2->wall].name + ".");
      c2->tmp = 100;
      return true;
      }
    if(c2->wall == waThumper && !monstersnear(c2)) {
      cellwalker push = cwt;
      cwstep(push);
      cwspin(push, 3);
      cwstep(push);
      if((!passable(push.c, c2, false, true) || !passable(push.c, cwt.c, false, true) || push.c->item) && c2->type == 7) {
        cwstep(push);
        cwspin(push, 1);
        cwstep(push);
        }
      if(!passable(push.c, c2, false, true) || !passable(push.c, cwt.c, false, true) || push.c->item) {
        if(checkonly) return false;
        addMessage("No room to push the Thumper!");
        return false;
        }
      if(checkonly) return true;
      addMessage("You push the Thumper.");
      push.c->tmp = c2->tmp;
      if(c2->land == laAlchemist)
        c2->wall = (cwt.c->wall == waFloorB || cwt.c->wall == waFloorA) ? cwt.c->wall : push.c->wall;
      else c2->wall = waNone;
      push.c->wall = waThumper;
      }

    if(c2->monst && !isFriendly(c2)) {
      if(c2->monst == moWorm || c2->monst == moWormtail || c2->monst == moWormwait) {
        if(checkonly) return false;
        addMessage(s0 + "You cannot attack Sandworms directly!");
        return false;
        }
      
      if(c2->monst == moTentacle || c2->monst == moTentacletail || c2->monst == moTentaclewait) {
        if(checkonly) return false;
        addMessage(s0 + "You cannot attack Tentacles directly!");
        return false;
        }
      
      if(c2->monst == moShadow) {
        if(checkonly) return false;
        addMessage(s0 + "You cannot defeat the Shadow!");
        return false;
        }
      
      if(c2->monst == moGreater || c2->monst == moGreaterM) {
        if(checkonly) return false;
        addMessage(s0 + "You cannot defeat the Greater Demon yet!");
        return false;
        }
      
      if(monstersnear(cwt.c, c2)) {
        if(checkonly) return false;
        addMessage(s0 + "You would be killed by the " + minf[which].name + "!");
        return false;
        }

      if(checkNeedMove(checkonly))
        return false;
      
      if(checkonly) return true;
      addMessage(s0 + "You kill the " + minf[c2->monst].name + ".");

      int tk = tkills();
      killMonster(c2);
      
      int ntk = tkills();

      if(tk == 0 && ntk > 0)
        addMessage("That was easy, but groups could be dangerous.");
        
      if(tk < 10 && ntk >= 10)
        addMessage("Good to know that your fighting skills serve you well in this strange world.");

      if(tk < 50 && ntk >= 50)
        addMessage("You wonder where all these monsters go, after their death...");

      if(tk < 100 && ntk >= 100)
        addMessage("You feel that the souls of slain enemies pull you to the Graveyard...");
      
      gomirrors(0);
      }
    else if(!passable(c2, cwt.c, true, true)) {
      if(checkonly) return false;
      if(c2->wall == waFloorA || c2->wall == waFloorB)
        addMessage("Wrong color!");
      else
        addMessage(s0 + "You cannot move through the " + winf[c2->wall].name + "!");
      return false;
      }
    else {
      if(c2->item == itOrbYendor && !checkYendor(c2, checkonly)) {
        return false;
        }
      if(!hasSafeOrb(c2) && monstersnear(c2)) {
        if(checkonly) return false;
        addMessage(s0 + "The " + minf[which].name + " would kill you there!");
        return false;
        }
      if(checkonly) return true;
      flipplayer = true;
      if(c2->item && c2->land == laAlchemist) c2->wall = cwt.c->wall;
      
      int pg = gold();

      if(c2->item) {
        string s0 = "";
        if(gold() == 0)
          addMessage(s0 + "Wow! "+iinf[c2->item].name + "! This trip should be worth it!");
        else if(gold() == 1)
          addMessage(s0 + "For now, collect as much treasure as possible...");
        else if(gold() == 2)
          addMessage(s0 + "Prove yourself here, then find new lands, with new quests...");
        else if(!items[c2->item] && c2->item < firstnontreasure)
          addMessage(s0 + "You collect your first "+iinf[c2->item].name + "!");
        else if(c2->item == itKey)
          addMessage(s0 + "You have found the Key! Now unlock this Orb of Yendor!");
        else if(c2->item == itGreenStone && !items[itGreenStone])
          addMessage(s0 + "This orb is dead...");
        else if(c2->item == itGreenStone)
          addMessage(s0 + "Another Dead Orb.");
        else if(c2->item >= firstnontreasure)
          addMessage(s0 + "You have found the "+iinf[c2->item].name+"!");
        else if(items[c2->item] == 4 && maxgold() == 4) {
          addMessage(s0 + "You feel that the "+linf[c2->land].name+" becomes more dangerous.");
          addMessage(s0 + "With each "+iinf[c2->item].name+" you collect...");
          }
        else if(items[c2->item] == 9 && maxgold() == 9)
          addMessage(s0 + "Are there any magical orbs in the "+linf[c2->land].name+"?...");
        else if(items[c2->item] == 10 && maxgold() == 10) {
          addMessage(s0 + "You feel that the "+linf[c2->land].name+" slowly becomes dangerous...");
          addMessage(s0 + "Better find some other place.");
          }
        else if(c2->item == itSpice && items[itSpice] == 7)
          addMessage(s0 + "You have a vision of the future, fighting demons in Hell...");
        else if(c2->item == itElixir && items[itElixir] == 4)
          addMessage(s0 + "With this Elixir, your life should be long and prosperous...");
        else if(c2->item == itBone && items[itBone] == 6)
          addMessage(s0 + "The Necromancer's Totem contains hellish incantations...");
        else if(c2->item == itStatue && items[itStatue] == 6)
          addMessage(s0 + "The inscriptions on the Statue of Cthulhu point you toward your destiny...");
        else if(c2->item == itDiamond && items[itDiamond] == 8)
          addMessage(s0 + "Still, even greater treasures lie ahead...");
        else 
          addMessage(s0 + "You collect the "+iinf[c2->item].name + ".");
        }
      
      if(c2->item == itOrbSpeed) {
        items[c2->item] += 31;
        if(items[c2->item] > 77) items[c2->item] = 77;
        }
      else if(c2->item == itOrbLife) {
        cwt.c->monst = moGolem;
        }
      else if(c2->item == itOrbSafety) {
        activateSafety(c2->land);
        return true;
        }
      else if(c2->item == itOrbLightning) {
        items[c2->item] += 78;
        if(items[c2->item] > 777) items[c2->item] = 777;
        }
      else if(c2->item == itOrbFlash) {
        items[c2->item] += 78;
        if(items[c2->item] > 777) items[c2->item] = 777;
        }
      else if(c2->item == itOrbShield) {
        items[c2->item] += 16;
        if(items[c2->item] > 77) items[c2->item] = 77;
        }
      else if(c2->item == itOrbWinter) {
        items[c2->item] += 31;
        if(items[c2->item] > 77) items[c2->item] = 77;
        }
      else if(c2->item == itOrbDigging) {
        items[c2->item] += 78;
        if(items[c2->item] > 101) items[c2->item] = 101;
        }
      else if(c2->item == itOrbTeleport) {
        items[c2->item] += 78;
        if(items[c2->item] > 201) items[c2->item] = 201;
        }
      else if(c2->item == itOrbYendor) {
        items[itOrbSpeed] += 31;
        items[itOrbLightning] += 78;
        items[itOrbFlash] += 78;
        items[itOrbShield] += 31;
        items[itOrbWinter] += 151;
        items[itOrbDigging] += 151;
        items[itOrbTeleport] += 151;
        items[itOrbYendor]++;
        items[itKey]--;
        addMessage("CONGRATULATIONS!");
        }
      else if(c2->item == itKey) {
        for(int i=0; i<size(yi); i++) if(yi[i].path[YDIST-1] == c2)
          yi[i].found = true;
        items[itKey]++;
        }
      else {
        bool lhu = hellUnlocked();
        if(c2->item) items[c2->item]++;
        int g2 = gold();
        if(pg < 15 && g2 >= 15) 
          addMessage("Collect treasure to access more different lands...");
        if(pg < 30 && g2 >= 30)
          addMessage("You feel that you have enough treasure to access new lands!");
        if(pg < 45 && g2 >= 45)
          addMessage("Collect more treasures, there are still more lands waiting...");
        if(pg < 60 && g2 >= 60)
          addMessage("You feel that the stars are right, and you can access R'Lyeh!");
        if(pg < 75 && g2 >= 75)
          addMessage("Kill monsters and collect treasures, and you may get access to Hell...");
        if(pg < 90 && g2 >= 90) 
          addMessage("To access Hell, collect 10 treasures of each kind...");
        if(hellUnlocked() && !lhu) {
          addMessage("Abandon all hope, the gates of Hell are opened!");
          addMessage("And the Orbs of Yendor await!");
          }
        }
      c2->item = itNone;
      
      if(items[itOrbWinter] && isIcyLand(cwt.c)) {
        if(cwt.c->wall == waNone) 
          cwt.c->wall = waIcewall;
        cwt.c->heat = -1;
        /* for(int i=0; i<cwt.c->type; i++) {
          if(cwt.c->mov[i]->wall == waLake)
            cwt.c->mov[i]->wall = waFrozenLake;
          cwt.c->mov[i]->heat = -1;
          } */
        }
      
      if(c2->monst == moGolem || c2->monst == moGolemMoved) {
        addMessage(s0 + "You switch places with the " + minf[c2->monst].name + ".");
        cwt.c->monst = c2->monst;
        c2->monst = moNone;
        }
      else if(c2->monst) {
        addMessage(s0 + "You rejoin the " + minf[c2->monst].name + ".");
        killMonster(c2);
        }

      cwstep(cwt);      
      
      gomirrors(1);

      if(c2->wall == waMirror) {
        addMessage("The mirror shatters!");
        if(c2->land == laMirror) items[itShard]++;
        c2->wall = waNone;
        createMirrors(cwt.c, cwt.spin, moMimic);
        }

      if(c2->wall == waCloud) {
        addMessage("The cloud turns into a bunch of images!");
        if(c2->land == laMirror) items[itShard]++;
        c2->wall = waNone;
        createMimics(cwt.c, cwt.spin, moMimic);
        }
      
      if(c2->wall == 0 && c2->land == laMotion)
        c2->wall = waChasm;

      setdist(cwt.c, 0, NULL);
      }
    }
  else {
    if(checkNeedMove(checkonly))
      return false;
    if(monstersnear(cwt.c)) {
      if(checkonly) return false;
      addMessage(s0 + "The " + minf[which].name + " would get you!");
      return false;
      }
    if(checkonly) return true;
    if(d == -2 && items[itGreenStone] && cwt.c->item == itNone) {
      items[itGreenStone]--;
      if(false) {
        cwt.c->item = itNone;
        spill(cwt.c, eWall(cwt.c->wall ^ waFloorA ^ waFloorB), 3);
        addMessage(s0 + "The slime reacts with the " + iinf[itGreenStone].name+"!");
        }
      else {
        cwt.c->item = itGreenStone;
        addMessage(s0 + "You drop the " + iinf[itGreenStone].name + ".");
        }
      }
    }
  DEBT("bfs");
  bfs();
  DEBT("heat");
  heat();
  DEBT("rop");
  reduceOrbPowers();
  DEBT("mmo");
  if(!(1 & items[itOrbSpeed])) movemonsters();
  DEBT("lc");
  livecaves();
  DEBT("check");
  canmove = false;
  if(movepcto(-1, true)) canmove = true;
  for(int i=0; i<cwt.c->type; i++) 
    if(movepcto(1, true)) canmove = true;
  DEBT("done");
  return true;
  }

void teleportpc(cell *dest) {
  if(dest->monst) 
    addMessage("Cannot teleport on a monster!");
  else if(dest->item)
    addMessage("Cannot teleport on an item!");
  else if(!passable(dest, NULL, false, true))
    addMessage("Cannot teleport here!");
  else if(dest->cpdist > 7)
    addMessage("You cannot teleport that far away!");
  else {
    cwt.c = dest; cwt.spin = rand() % dest->type; flipplayer = !!(rand() % 2);
    items[itOrbTeleport] = 0;
  
    addMessage("You teleport to a new location!");

    for(int i=9; i>=0; i--)
      setdist(cwt.c, i, NULL);

    bfs();
  
    canmove = false;
    if(movepcto(-1, true)) canmove = true;
    for(int i=0; i<cwt.c->type; i++) 
      if(movepcto(1, true)) canmove = true;

    }
  }

void saveStats() {
#ifndef ANDROID
  FILE *f = fopen(scorefile, "at");
  if(!f) {
    printf("Could not open the score file '%s'!\n", scorefile);
    addMessage(s0 + "Could not open the score file: " + scorefile);
    return;
    }

  if(showoff) return;
  
  time_t timer;
  timer = time(NULL);
  char sbuf[128]; strftime(sbuf, 128, "%c", localtime(&timerstart));
  char buf[128]; strftime(buf, 128, "%c", localtime(&timer));
  
  fprintf(f, "HyperRogue: game statistics (version "VER")\n");
  if(cheater)
    fprintf(f, "CHEATER! (cheated %d times)\n", cheater);
  else {
    fprintf(f, VER " %d %d %d %d %d %d", int(timerstart), int(timer), gold(), tkills(), turncount, cellcount);
    for(int i=0; i<firstorb; i++) fprintf(f, " %d", items[i]);
    for(int i=0; i<motypes; i++) fprintf(f," %d", kills[i]);
    fprintf(f, "\n");
    }
  fprintf(f, "Played on: %s - %s (%d turns)\n", sbuf, buf, turncount);
  fprintf(f, "Total wealth: %d\n", gold());
  fprintf(f, "Total enemies killed: %d\n", tkills());
  fprintf(f, "cells generated: %d\n", cellcount);
  fprintf(f, "Number of cells explored, by distance from the player:\n"); 
  for(int i=0; i<10; i++) fprintf(f, " %d", explore[i]); fprintf(f, "\n");
  if(kills[0]) fprintf(f, "walls melted: %d\n", kills[0]);
  fprintf(f, "heptagons travelled: %d\n", celldist(cwt.c));
  
  fprintf(f, "\n");

  for(int i=0; i<firstorb; i++) if(items[i])  
    fprintf(f, "%4dx %s\n", items[i], iinf[i].name);
    
  fprintf(f, "\n");
  
  for(int i=1; i<motypes; i++) if(kills[i])  
    fprintf(f, "%4dx %s\n", kills[i], minf[i].name);
  
  fprintf(f, "\n\n\n");
  
  printf("Game statistics saved to %s\n", scorefile);
  addMessage(s0 + "Game statistics saved to " + scorefile);
  fclose(f);
#endif
  }

void restartGame() {
  DEB("savestats");
  saveStats();
  DEB("clear");
  for(int i=0; i<ittypes; i++) items[i] = 0;
  for(int i=0; i<motypes; i++) kills[i] = 0;
  for(int i=0; i<10; i++) explore[i] = 0;
  // items[itGreenStone] = 100;
  cellcount = 0;
  DEB("clearmem");
  clearMemory();
  DEB("initc");
  initcells();
  DEB("initg");
  initgame();
  canmove = true;
  DEB("restg");
  extern void restartGraph();
  restartGraph();
  extern void resetmusic();
  resetmusic();
  }

void clearGameMemory() {
  pathq.clear();
  dcal.clear();
  yii = 0; yi.clear();
  clearshadow();
  }
