/**
   * Move the player or open closed doors, remove any monsters the player bumped, then update the
   * DijkstraMap and have the monsters that can see the player try to approach. In a fully-fledged
   * game, this would not be organized like this, but this is a one-file demo.
   *
   * @param xmod
   * @param ymod
   */
  private void move(int xmod, int ymod) {
    clearHelp();

    if (health <= 0) return;

    int newX = player.gridX + xmod, newY = player.gridY + ymod;
    if (newX >= 0 && newY >= 0 && newX < width && newY < height && bareDungeon[newX][newY] != '#') {
      // '+' is a door.
      if (decoDungeon[newX][newY] == '+') {
        decoDungeon[newX][newY] = '/';
        lineDungeon[newX * 2][newY] = '/';
        // changes to the map mean the resistances for FOV need to be regenerated.
        res = DungeonUtility.generateResistances(decoDungeon);
        // recalculate FOV, store it in fovmap for the render to use.
        fovmap = fov.calculateFOV(res, player.gridX, player.gridY, 8, Radius.SQUARE);
      } else {
        // recalculate FOV, store it in fovmap for the render to use.
        fovmap = fov.calculateFOV(res, newX, newY, 8, Radius.SQUARE);
        display.slide(player, newX, newY);
        monsters.remove(Coord.get(newX, newY));
      }
      if (newX == dungeonGen.stairsDown.x && newY == dungeonGen.stairsDown.y) {
        messages.appendMessage("WINNER WINNER CHICKEN DINNER!");
      }

      phase = Phase.PLAYER_ANIM;
    }
  }
  private void postMove() {

    phase = Phase.MONSTER_ANIM;
    // The next two lines are important to avoid monsters treating cells the player WAS in as goals.
    getToPlayer.clearGoals();
    getToPlayer.resetMap();
    // now that goals are cleared, we can mark the current player position as a goal.
    getToPlayer.setGoal(player.gridX, player.gridY);
    // this is an important piece of DijkstraMap usage; the argument is a Set of Points for squares
    // that
    // temporarily cannot be moved through (not walls, which are automatically known because the map
    // char[][]
    // was passed to the DijkstraMap constructor, but things like moving creatures and objects).
    LinkedHashSet<Coord> monplaces = monsters.positions();

    pathMap = getToPlayer.scan(monplaces);

    // recalculate FOV, store it in fovmap for the render to use.
    fovmap = fov.calculateFOV(res, player.gridX, player.gridY, 8, Radius.SQUARE);
    // handle monster turns
    ArrayList<Coord> nextMovePositions = new ArrayList<Coord>(25);
    for (Coord pos : monsters.positions()) {
      Monster mon = monsters.get(pos);
      // monster values are used to store their aggression, 1 for actively stalking the player, 0
      // for not.
      if (mon.state > 0 || fovmap[pos.x][pos.y] > 0.1) {
        if (mon.state == 0) {
          messages.appendMessage(
              "The PHANTOM cackles at you, \""
                  + FakeLanguageGen.RUSSIAN_AUTHENTIC.sentence(
                      rng, 1, 3, new String[] {",", ",", ",", " -"}, new String[] {"!"}, 0.25)
                  + "\"");
        }
        // this block is used to ensure that the monster picks the best path, or a random choice if
        // there
        // is more than one equally good best option.
        Direction choice = null;
        double best = 9999.0;
        Direction[] ds = new Direction[8];
        rng.shuffle(Direction.OUTWARDS, ds);
        for (Direction d : ds) {
          Coord tmp = pos.translate(d);
          if (pathMap[tmp.x][tmp.y] < best && !checkOverlap(mon, tmp.x, tmp.y, nextMovePositions)) {
            // pathMap is a 2D array of doubles where 0 is the goal (the player).
            // we use best to store which option is closest to the goal.
            best = pathMap[tmp.x][tmp.y];
            choice = d;
          }
        }
        if (choice != null) {
          Coord tmp = pos.translate(choice);
          // if we would move into the player, instead damage the player and give newMons the
          // current
          // position of this monster.
          if (tmp.x == player.gridX && tmp.y == player.gridY) {
            display.tint(player.gridX * 2, player.gridY, SColor.PURE_CRIMSON, 0, 0.415f);
            display.tint(player.gridX * 2 + 1, player.gridY, SColor.PURE_CRIMSON, 0, 0.415f);
            health--;
            // player.setText("" + health);
            monsters.positionalModify(pos, mon.change(1));
          }
          // otherwise store the new position in newMons.
          else {
            /*if (fovmap[mon.getKey().x][mon.getKey().y] > 0.0) {
                display.put(mon.getKey().x, mon.getKey().y, 'M', 11);
            }*/
            nextMovePositions.add(tmp);
            monsters.positionalModify(pos, mon.change(1));
            monsters.move(pos, tmp);
            display.slide(mon.entity, tmp.x, tmp.y);
          }
        } else {
          monsters.positionalModify(pos, mon.change(1));
        }
      }
    }
  }