--- /dev/null
+/*
+ engine.c
+
+ Copyright (C) 2010-2019 Amf
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "chroma.h"
+#include "level.h"
+#include "util.h"
+
+#ifdef XOR_COMPATIBILITY
+int xor_move(struct level* plevel, int move);
+int xor_evolve(struct level* plevel);
+void xor_focus(struct level* plevel);
+extern int options_xor_mode;
+#endif
+#ifdef ENIGMA_COMPATIBILITY
+int enigma_move(struct level* plevel, int move);
+int enigma_evolve(struct level* plevel);
+extern int options_enigma_mode;
+#endif
+
+extern int options_debug;
+extern char* piece_name[];
+
+/* l u r d n s w */
+int move_x[] = {-1, 0, 1, 0, 0, 0, 0};
+int move_y[] = {0, -1, 0, 1, 0, 0, 0};
+
+#ifdef ENIGMA_COMPATIBILITY
+int enigma_move_order[] = {MOVE_DOWN, MOVE_RIGHT, MOVE_LEFT, MOVE_UP};
+#endif
+#ifdef XOR_COMPATIBILITY
+int xor_teleport_order[] = {MOVE_RIGHT, MOVE_UP, MOVE_LEFT, MOVE_DOWN};
+#endif
+
+struct mover* mover_new(struct level* plevel, int x, int y, int d, int piece, int fast);
+void mover_consider(struct level* plevel, int x, int y, int d);
+struct mover* mover_explode(struct level *plevel, int x, int y, int d, int p);
+void explode_sides(struct level* plevel, int x, int y, int p, int d);
+int canfall(int p, int into, int d);
+int canmove(int p, int into, int d, int fast);
+int canexplode(int p, int into, int d, int fast, int mode);
+int canbepushed(int p, int into, int d, int mode);
+
+int explosiontype(int p)
+{
+ switch(p)
+ {
+ case PIECE_ARROW_RED_LEFT:
+ case PIECE_ARROW_RED_RIGHT:
+ case PIECE_BOMB_RED_LEFT:
+ case PIECE_BOMB_RED_RIGHT:
+ return PIECE_EXPLOSION_NEW_RED_VERTICAL;
+ case PIECE_ARROW_RED_UP:
+ case PIECE_ARROW_RED_DOWN:
+ case PIECE_BOMB_RED_UP:
+ case PIECE_BOMB_RED_DOWN:
+ return PIECE_EXPLOSION_NEW_RED_HORIZONTAL;
+
+ case PIECE_ARROW_GREEN_LEFT:
+ case PIECE_ARROW_GREEN_RIGHT:
+ case PIECE_BOMB_GREEN_LEFT:
+ case PIECE_BOMB_GREEN_RIGHT:
+ return PIECE_EXPLOSION_NEW_GREEN_VERTICAL;
+ case PIECE_ARROW_GREEN_UP:
+ case PIECE_ARROW_GREEN_DOWN:
+ case PIECE_BOMB_GREEN_UP:
+ case PIECE_BOMB_GREEN_DOWN:
+ return PIECE_EXPLOSION_NEW_GREEN_HORIZONTAL;
+
+ case PIECE_ARROW_BLUE_LEFT:
+ case PIECE_ARROW_BLUE_RIGHT:
+ case PIECE_BOMB_BLUE_LEFT:
+ case PIECE_BOMB_BLUE_RIGHT:
+ return PIECE_EXPLOSION_NEW_BLUE_VERTICAL;
+ case PIECE_ARROW_BLUE_UP:
+ case PIECE_ARROW_BLUE_DOWN:
+ case PIECE_BOMB_BLUE_UP:
+ case PIECE_BOMB_BLUE_DOWN:
+ return PIECE_EXPLOSION_NEW_BLUE_HORIZONTAL;
+
+ default:
+ /* This should never happen */
+ return PIECE_GONE;
+ }
+}
+
+void level_moved(struct level* plevel, int move)
+{
+ if(move != MOVE_REDO)
+ level_addmove(plevel, move);
+ else
+ {
+ if(plevel->move_current != NULL)
+ plevel->move_current = plevel->move_current->next;
+ else
+ plevel->move_current = plevel->move_first;
+ }
+
+ plevel->moves ++;
+ plevel->flags |= LEVELFLAG_MOVES;
+
+ level_storemovers(plevel);
+}
+
+int level_move(struct level* plevel, int move)
+{
+ int x, y;
+ int tx, ty;
+ int px, py;
+ int p;
+ int ok;
+#ifdef XOR_COMPATIBILITY
+ int dx, dy;
+ int teleport;
+ int td;
+ int i;
+#endif
+ int realmove;
+ struct move* pmove;
+
+ realmove = move;
+
+ if(plevel == NULL || plevel->mover_first != NULL)
+ return 0;
+
+ if(move == MOVE_REDO)
+ {
+ if(plevel->move_current == NULL)
+ pmove = plevel->move_first;
+ else
+ pmove = plevel->move_current->next;
+
+ if(pmove == NULL)
+ return 0;
+
+ move = pmove->direction;
+ }
+
+ if(move == MOVE_SWAP)
+ {
+ if(plevel->alive[1 - plevel->player])
+ {
+ plevel->player = 1 - plevel->player;
+
+ /* Create new movers for the stationary swapped players to allow
+ the display to redraw them after the swap. */
+ mover_new(plevel, plevel->player_x[plevel->player], plevel->player_y[plevel->player], MOVE_SWAP, PIECE_PLAYER_ONE + plevel->player, 0);
+
+ /* Is the first player still alive? */
+ if(plevel->alive[1 - plevel->player])
+ mover_new(plevel, plevel->player_x[1 - plevel->player], plevel->player_y[1 - plevel->player], MOVE_SWAPPED, PIECE_PLAYER_ONE + 1 - plevel->player, 0);
+
+ level_moved(plevel, realmove);
+
+ return 1;
+ }
+ return 0;
+ }
+
+ if(plevel->alive[plevel->player] == 0)
+ return 0;
+
+#ifdef XOR_COMPATIBILITY
+ if(plevel->mode == MODE_XOR && options_xor_mode == 1)
+ {
+ if(xor_move(plevel, move))
+ {
+ level_moved(plevel, realmove);
+ xor_focus(plevel);
+ return 1;
+ }
+ return 0;
+ }
+#endif
+#ifdef ENIGMA_COMPATIBILITY
+ if(plevel->mode == MODE_ENIGMA && options_enigma_mode == 1)
+ {
+ if(enigma_move(plevel, move))
+ {
+ level_moved(plevel, realmove);
+ return 1;
+ }
+ return 0;
+ }
+
+#endif
+
+ /* Consider where we are moving to */
+ x = plevel->player_x[plevel->player] + move_x[move];
+ y = plevel->player_y[plevel->player] + move_y[move];
+
+ p = level_piece(plevel, x, y);
+
+ ok = 0;
+
+ /* Can we move into the piece in that direction? */
+ switch(p)
+ {
+ case PIECE_DOOR:
+ if(plevel->stars_caught == plevel->stars_total)
+ {
+ plevel->flags |= LEVELFLAG_EXIT;
+ ok = 1;
+ }
+ break;
+
+ case PIECE_STAR:
+ plevel->stars_caught ++;
+ plevel->flags |= LEVELFLAG_STARS;
+ ok = 1;
+ break;
+
+#ifdef XOR_COMPATIBILITY
+ case PIECE_TELEPORT:
+ /* Only XOR has teleports. We force the issue so as not to break
+ Chroma's rotational symmetry by introducing teleport order. */
+ if(plevel->mode != MODE_XOR)
+ break;
+
+ teleport = -1;
+ if(x == plevel->teleport_x[0] && y == plevel->teleport_y[0])
+ teleport = 0;
+ if(x == plevel->teleport_x[1] && y == plevel->teleport_y[1])
+ teleport = 1;
+ if(teleport != -1)
+ {
+ tx = plevel->teleport_x[1 - teleport];
+ ty = plevel->teleport_y[1 - teleport];
+ td = move;
+
+ /* Does the other teleport still exist? */
+ if(level_piece(plevel, tx, ty) == PIECE_TELEPORT)
+ {
+ ok = 0;
+ /* Find the first available exit from it */
+ for(i = 0; i < 4; i ++)
+ {
+ dx = tx + move_x[xor_teleport_order[i]];
+ dy = ty + move_y[xor_teleport_order[i]];
+ if(!ok && level_piece(plevel, dx, dy) == PIECE_SPACE)
+ {
+ /* Change move to produce the effect of coming
+ out of the teleport */
+ x = dx; y = dy; move = xor_teleport_order[i];
+ ok = 1;
+ }
+ }
+
+ if(ok)
+ {
+ /* Visual effects for the player going in one teleport */
+ /* Store original player move direction in cosmetic mover */
+ mover_new(plevel, plevel->teleport_x[teleport], plevel->teleport_y[teleport], td, PIECE_TELEPORT, 0);
+ level_setprevious(plevel, plevel->teleport_x[teleport], plevel->teleport_y[teleport], PIECE_PLAYER_ONE + plevel->player);
+ level_setpreviousmoving(plevel, plevel->teleport_x[teleport], plevel->teleport_y[teleport], realmove);
+ /* and out of the other teleport */
+ mover_new(plevel, plevel->teleport_x[1 - teleport], plevel->teleport_y[1 - teleport], MOVE_NONE, PIECE_TELEPORT, 0);
+
+ /* Change the viewpoint to that of the other teleport */
+ plevel->view_x[plevel->player] = plevel->view_teleport_x[1 - teleport];
+ plevel->view_y[plevel->player] = plevel->view_teleport_y[1 - teleport];
+
+ }
+ }
+ }
+ break;
+
+ case PIECE_SWITCH:
+ plevel->switched = 1 - plevel->switched;
+ plevel->flags |= LEVELFLAG_SWITCH;
+ ok = 1;
+ break;
+
+ case PIECE_MAP_TOP_LEFT:
+ plevel->mapped |= MAPPED_TOP_LEFT;
+ plevel->flags |= LEVELFLAG_MAP;
+ ok = 1;
+ break;
+ case PIECE_MAP_TOP_RIGHT:
+ plevel->mapped |= MAPPED_TOP_RIGHT;
+ plevel->flags |= LEVELFLAG_MAP;
+ ok = 1;
+ break;
+ case PIECE_MAP_BOTTOM_LEFT:
+ plevel->mapped |= MAPPED_BOTTOM_LEFT;
+ plevel->flags |= LEVELFLAG_MAP;
+ ok = 1;
+ break;
+ case PIECE_MAP_BOTTOM_RIGHT:
+ plevel->mapped |= MAPPED_BOTTOM_RIGHT;
+ plevel->flags |= LEVELFLAG_MAP;
+ ok = 1;
+ break;
+
+ case PIECE_DOTS_X:
+ if(move == MOVE_LEFT || move == MOVE_RIGHT)
+ ok = 1;
+ break;
+
+ case PIECE_DOTS_Y:
+ if(move == MOVE_UP || move == MOVE_DOWN)
+ ok = 1;
+ break;
+#endif
+
+#ifdef ENIGMA_COMPATIBILITY
+ case PIECE_DOTS_DOUBLE:
+#endif
+ case PIECE_DOTS:
+ case PIECE_SPACE:
+ ok = 1;
+ break;
+ }
+
+ /* Is there a piece we can push? */
+ if(!ok)
+ {
+ tx = x + move_x[move];
+ ty = y + move_y[move];
+
+ if(canbepushed(p, level_piece(plevel, tx, ty), move, plevel->mode))
+ {
+ mover_new(plevel, tx, ty, move, p, 0);
+ ok = 1;
+ }
+ }
+
+ if(ok)
+ {
+
+ /* Cosmetic mover for storing the player's direction in undo */
+ mover_new(plevel, plevel->player_x[plevel->player], plevel->player_y[plevel->player], move, PIECE_GONE, 0);
+
+ mover_new(plevel, x, y, move, PIECE_PLAYER_ONE + plevel->player, 0);
+
+ px = plevel->player_x[plevel->player];
+ py = plevel->player_y[plevel->player];
+
+#ifdef XOR_COMPATIBILITY
+ /* XOR protects the players move */
+ if(plevel->mode == MODE_XOR)
+ {
+ /* Blank the player's space first to avoid upsetting undo */
+ level_setpiece(plevel, px, py, PIECE_SPACE);
+ mover_new(plevel, px, py, (move + 1) % 4, PIECE_SPACE, 1);
+ }
+ /* Chroma lets a piece follow in the player's trail */
+ else
+ {
+#endif
+ /* Blank the player's space first to avoid upsetting undo */
+ level_setpiece(plevel, px, py, PIECE_SPACE);
+ mover_consider(plevel, px, py, move % 4);
+#ifdef XOR_COMPATIBILITY
+ }
+#endif
+
+ plevel->player_x[plevel->player] = x;
+ plevel->player_y[plevel->player] = y;
+
+ level_moved(plevel, realmove);
+
+#ifdef XOR_COMPATIBILITY
+ if(plevel->mode == MODE_XOR)
+ xor_focus(plevel);
+#endif
+
+ return 1;
+ }
+
+ return 0;
+}
+
+struct mover* mover_explode(struct level *plevel, int x, int y, int d, int p)
+{
+ /* Don't explode any of the edge wall */
+ if(x == 0 || y == 0 || x == plevel->size_x - 1 || y == plevel->size_y - 1)
+ return NULL;
+
+ /* What have we exploded? */
+ switch(level_piece(plevel, x, y))
+ {
+ case PIECE_STAR:
+ plevel->stars_exploded ++;
+ plevel->flags |= LEVELFLAG_STARS;
+ break;
+
+#ifdef XOR_COMPATIBILITY
+ case PIECE_SWITCH:
+ plevel->switched = 1 - plevel->switched;
+ plevel->flags |= LEVELFLAG_SWITCH;
+ break;
+#endif
+ }
+
+ return mover_new(plevel, x, y, d, p, 1);
+}
+
+struct mover* mover_new(struct level* plevel, int x, int y, int d, int piece, int fast)
+{
+ struct mover* pmover;
+ int previous;
+ int data;
+
+ /* Don't allow two movers in the same space, unless one is exploding */
+ if(!isnewexplosion(piece) && level_moving(plevel, x, y) != MOVE_NONE)
+ return NULL;
+
+ pmover = (struct mover*)malloc(sizeof(struct mover));
+ if(pmover == NULL)
+ fatal("Out of memory in mover_new()");
+
+ previous = level_piece(plevel, x, y);
+
+ pmover->x = x;
+ pmover->y = y;
+ pmover->direction = d;
+ pmover->piece = piece;
+ pmover->piece_previous = previous;
+ pmover->fast = fast;
+ pmover->next = NULL;
+ pmover->previous = plevel->mover_last;
+
+ if(plevel->mover_first == NULL)
+ plevel->mover_first = pmover;
+
+ if(plevel->mover_last != NULL)
+ plevel->mover_last->next = pmover;
+
+ plevel->mover_last = pmover;
+
+ /* Show pieces collected by players */
+ if(piece == PIECE_PLAYER_ONE || piece == PIECE_PLAYER_TWO)
+ {
+ if((previous < PIECE_MOVERS_FIRST || previous > PIECE_MOVERS_LAST)
+ && previous != PIECE_CIRCLE
+#ifdef ENIGMA_COMPATIBILITY
+ && previous != PIECE_CIRCLE_DOUBLE
+#endif
+ && previous != PIECE_PLAYER_ONE
+ && previous != PIECE_PLAYER_TWO)
+ level_setprevious(plevel, x, y, previous);
+ }
+
+ /* Show players squashed by movers */
+ if(piece >= PIECE_MOVERS_FIRST && piece <= PIECE_MOVERS_LAST)
+ {
+ if(previous == PIECE_PLAYER_ONE || previous == PIECE_PLAYER_TWO)
+ level_setprevious(plevel, x, y, previous);
+ }
+
+ /* Show pieces removed by movers or explosions */
+ if(previous == PIECE_DOTS
+#ifdef ENIGMA_COMPATIBILITY
+ || previous == PIECE_DOTS_DOUBLE
+#endif
+#ifdef XOR_COMPATIBILITY
+ || previous == PIECE_DOTS_X
+ || previous == PIECE_DOTS_Y
+#endif
+ || isexplosion(previous))
+ level_setprevious(plevel, x, y, previous);
+
+ /* Show exploded pieces */
+ if(isnewexplosion(piece) && !isnewexplosion(previous))
+ {
+ level_setprevious(plevel, x, y, previous);
+ level_setpreviousmoving(plevel, x, y, level_moving(plevel, x, y));
+ }
+
+ /* Explosions occur later */
+ if(!isnewexplosion(piece) && piece != PIECE_GONE)
+ {
+ level_setpiece(plevel, x, y, piece);
+ level_setmoving(plevel, x, y, d);
+ }
+
+ /* Maintain piece graphic */
+ if(d != MOVE_NONE)
+ {
+ data = level_data(plevel, x - move_x[d], y - move_y[d]) & 0xff00;
+ data = (level_data(plevel, x, y) & ~0xff00) | data;
+ level_setdata(plevel, x, y, data);
+ }
+
+ return pmover;
+}
+
+struct mover* mover_addtostack(struct level* plevel, int x, int y, int move)
+{
+ struct mover* pmover;
+
+ pmover = (struct mover*)malloc(sizeof(struct mover));
+ if(pmover == NULL)
+ fatal("Out of memory in mover_addtostack()");
+
+ pmover->x = x;
+ pmover->y = y;
+ pmover->direction = move;
+ pmover->piece = PIECE_SPACE;
+ pmover->fast = 0;
+ pmover->next = NULL;
+
+ if(plevel->stack_first == NULL)
+ plevel->stack_first = pmover;
+
+ if(plevel->stack_last != NULL)
+ plevel->stack_last->next = pmover;
+
+ plevel->stack_last = pmover;
+
+ return pmover;
+}
+
+void level_storemovers(struct level* plevel)
+{
+ struct mover* pmover;
+ int previous;
+
+ int count = 0;
+
+ if(plevel->move_current == NULL || plevel->mover_first == NULL)
+ return;
+
+ if((options_debug & DEBUG_MOVERS) && plevel->move_current->mover_first == NULL)
+ fprintf(stderr, "\n");
+
+ pmover = plevel->mover_first;
+ while(pmover != NULL)
+ {
+ /* If something is moving into an explosion, don't store it as the
+ previous piece for this space; it will have its own mover, and thus
+ will be stored elsewhere. */
+ previous = pmover->piece_previous;
+ if(isexplosion(pmover->piece) && level_previousmoving(plevel, pmover->x, pmover->y) != MOVE_NONE)
+ previous = PIECE_SPACE;
+
+ mover_newundo(plevel, pmover->x, pmover->y,
+ pmover->direction, pmover->piece, previous,
+ MOVER_STORE | (pmover->next == NULL ? 0 : MOVER_FAST));
+
+ if(options_debug & DEBUG_MOVERS)
+ fprintf(stderr, "[%d] Storing undo mover at (%d,%d) is %s was %s (direction=%c) (flags=%d)\n",
+ count ++, pmover->x, pmover->y,
+ piece_name[pmover->piece], piece_name[previous],
+ directiontochar(pmover->direction),
+ (pmover->next == NULL ? 0 : MOVER_FAST));
+
+ pmover = pmover->next;
+ }
+}
+
+int level_evolve(struct level* plevel)
+{
+ struct mover* poldmovers;
+ struct mover* pmover;
+ int x, y;
+ int i;
+ int d;
+ int ad;
+ int ed;
+ int ax, ay;
+ int bp, bd;
+ int filled;
+
+#ifdef XOR_COMPATIBILITY
+ if(plevel->mode == MODE_XOR && options_xor_mode == 1)
+ {
+ return xor_evolve(plevel);
+ }
+#endif
+#ifdef ENIGMA_COMPATIBILITY
+ if(plevel->mode == MODE_ENIGMA && options_enigma_mode == 1)
+ {
+ return enigma_evolve(plevel);
+ }
+#endif
+
+ poldmovers = plevel->mover_first;
+
+ plevel->mover_first = NULL;
+ plevel->mover_last = NULL;
+
+ /* Chroma's engine isn't perfect. Pieces that appear to be in continuous
+ motion are actually momentarily stationary at the start of every cycle.
+ In pathological cases, this can give rise to some counterintuitive
+ situations, where the outcome depends on the order of the movers.
+
+ See levels/regression/chroma-regression.chroma for some examples.
+ */
+
+ pmover = poldmovers;
+ while(pmover != NULL)
+ {
+ level_setmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
+ level_setprevious(plevel, pmover->x, pmover->y, PIECE_SPACE);
+ level_setpreviousmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
+ level_setdetonator(plevel, pmover->x, pmover->y, PIECE_SPACE);
+ level_setdetonatormoving(plevel, pmover->x, pmover->y, MOVE_NONE);
+ pmover = pmover->next;
+ }
+
+ pmover = poldmovers;
+ while(pmover != NULL)
+ {
+
+ /* Remove the mover if something has already moved into its space */
+ if(level_moving(plevel, pmover->x, pmover->y) != MOVE_NONE
+ /* or it isn't what it should be */
+ || level_piece(plevel, pmover->x, pmover->y) != pmover->piece
+ )
+ pmover->piece = PIECE_GONE;
+
+ switch(pmover->piece)
+ {
+ case PIECE_SPACE:
+ case PIECE_EXPLOSION_RED_LEFT:
+ case PIECE_EXPLOSION_RED_HORIZONTAL:
+ case PIECE_EXPLOSION_RED_RIGHT:
+ case PIECE_EXPLOSION_RED_TOP:
+ case PIECE_EXPLOSION_RED_VERTICAL:
+ case PIECE_EXPLOSION_RED_BOTTOM:
+ case PIECE_EXPLOSION_GREEN_LEFT:
+ case PIECE_EXPLOSION_GREEN_HORIZONTAL:
+ case PIECE_EXPLOSION_GREEN_RIGHT:
+ case PIECE_EXPLOSION_GREEN_TOP:
+ case PIECE_EXPLOSION_GREEN_VERTICAL:
+ case PIECE_EXPLOSION_GREEN_BOTTOM:
+ case PIECE_EXPLOSION_BLUE_LEFT:
+ case PIECE_EXPLOSION_BLUE_HORIZONTAL:
+ case PIECE_EXPLOSION_BLUE_RIGHT:
+ case PIECE_EXPLOSION_BLUE_TOP:
+ case PIECE_EXPLOSION_BLUE_VERTICAL:
+ case PIECE_EXPLOSION_BLUE_BOTTOM:
+ i = 0;
+ filled = 0;
+
+ /* Consider the pieces around the space */
+ for(i = 0; i < 4; i ++)
+ {
+ if(filled)
+ continue;
+
+#ifdef ENIGMA_COMPATIBILITY
+ /* Enigma has a fixed move order */
+ if(plevel->mode == MODE_ENIGMA)
+ d = enigma_move_order[i];
+ else
+#endif
+ /* Chroma and XOR depend on how the space was emptied */
+ d = (pmover->direction + i) % 4;
+
+ ad = (d + 2) % 4;
+ ax = pmover->x + move_x[ad];
+ ay = pmover->y + move_y[ad];
+
+ /* Can the piece move into the space? */
+ if(canfall(level_piece(plevel, ax, ay), PIECE_SPACE, d)
+ /* and that piece isn't already moving */
+ && level_moving(plevel, ax, ay) == MOVE_NONE
+ )
+ {
+ x = pmover->x + move_x[d];
+ y = pmover->y + move_y[d];
+
+ /* Can the piece from the opposite direction also
+ move into this space? */
+ if(canfall(level_piece(plevel, x, y), PIECE_SPACE, ad)
+ /* and that piece isn't already moving */
+ && level_moving(plevel, x, y) == MOVE_NONE
+ /* If so, can the two explode? */
+ && canexplode(level_piece(plevel, ax, ay), level_piece(plevel, x, y), d, 1, plevel->mode)
+ /* (but not for XOR and Enigma) */
+ && plevel->mode == MODE_CHROMA
+ )
+ {
+ /* If so, detonate them in the middle */
+ if((level_piece(plevel, x, y) & 4) == 4)
+ {
+ /* The first piece is the bomb */
+ bp = level_piece(plevel, x, y);
+ bd = ad;
+ ed = level_piece(plevel, x, y) & 3;
+
+ level_setdetonator(plevel, pmover->x, pmover->y, level_piece(plevel, ax, ay));
+ level_setdetonatormoving(plevel, pmover->x, pmover->y, d);
+ }
+ else
+ {
+ /* The second piece is the bomb */
+ bp = level_piece(plevel, ax, ay);
+ bd = d;
+ ed = level_piece(plevel, ax, ay) & 3;
+
+ level_setdetonator(plevel, pmover->x, pmover->y, level_piece(plevel, x, y));
+ level_setdetonatormoving(plevel, pmover->x, pmover->y, ad);
+ }
+
+ /* and consider anything following them */
+ mover_consider(plevel, x, y, ad);
+ mover_consider(plevel, ax, ay, d);
+
+ /* Move the bomb into the space */
+ level_setpiece(plevel, pmover->x, pmover->y, bp);
+ level_setmoving(plevel, pmover->x, pmover->y, bd);
+
+ /* and explode it */
+ mover_explode(plevel, pmover->x, pmover->y, ed, explosiontype(bp));
+
+ /* Create the central explosion now, to prevent the
+ piece there being processed as a later mover. */
+ level_setpiece(plevel, pmover->x, pmover->y, explosiontype(bp));
+
+ explode_sides(plevel, pmover->x, pmover->y, bp, ed);
+
+ filled = 1;
+ break;
+ }
+
+ /* Otherwise, keep the piece moving */
+ mover_new(plevel, pmover->x, pmover->y, d, level_piece(plevel, ax, ay), 1);
+ /* and see if anything is following in its trail */
+ mover_consider(plevel, ax, ay, d);
+
+ filled = 1;
+ break;
+ }
+ }
+
+ /* If the explosion has not been filled */
+ if(isexplosion(pmover->piece) && filled == 0
+ /* and nothing else is moving into it */
+ && level_moving(plevel, pmover->x, pmover->y) == MOVE_NONE
+ )
+ /* then turn it into a space */
+ mover_new(plevel, pmover->x, pmover->y, pmover->direction, PIECE_SPACE, 0);
+
+ break;
+
+ case PIECE_PLAYER_ONE:
+ case PIECE_PLAYER_TWO:
+ case PIECE_GONE:
+ /* These 'movers' are purely for cosmetic purposes */
+ break;
+
+#ifdef XOR_COMPATIBILITY
+ case PIECE_TELEPORT:
+ /* These 'movers' are purely for cosmetic purposes */
+ break;
+#endif
+
+ default:
+ /* A pushed arrow still falls in its natural direction */
+ if(pmover->fast == 0 && pmover->piece >= PIECE_MOVERS_FIRST && pmover->piece <= PIECE_MOVERS_LAST)
+ pmover->direction = pmover->piece % 4;
+
+ /* Consider the space in front of the mover */
+ x = pmover->x + move_x[pmover->direction];
+ y = pmover->y + move_y[pmover->direction];
+
+ /* Can the mover move into the space in front of it? */
+ if(canmove(pmover->piece, level_piece(plevel, x, y), pmover->direction, pmover->fast)
+ /* and that space doesn't already have something
+ moving into it */
+ && (level_moving(plevel, x, y) == MOVE_NONE)
+ )
+ {
+ /* If so, keep it moving */
+ mover_new(plevel, x, y, pmover->direction, pmover->piece, 1);
+ /* and see if anything is following in its trail */
+ mover_consider(plevel, pmover->x, pmover->y, pmover->direction);
+ break;
+ }
+
+ /* Can the mover explode the piece in front of it? */
+ if(canexplode(pmover->piece, level_piece(plevel, x, y), pmover->direction, pmover->fast, plevel->mode)
+ /* and the piece in front isn't moving */
+ && (level_moving(plevel, x, y) == MOVE_NONE
+ /* or it is moving towards us */
+ || (level_moving(plevel, x, y) == ((pmover->direction + 2) % 4)
+ /* (but not for XOR or Enigma) */
+ && plevel->mode==MODE_CHROMA))
+ )
+ {
+ bp = level_piece(plevel, x, y);
+ level_setdetonator(plevel, x, y, pmover->piece);
+ level_setdetonatormoving(plevel, x, y, pmover->direction);
+
+ /* Explosion direction is bomb fall direction */
+ if(bp & 4)
+ ed = bp & 3;
+ else
+ ed = pmover->piece & 3;
+
+ mover_explode(plevel, x, y, ed, explosiontype(bp));
+
+ /* Create the central explosion now, to prevent the piece
+ there being processed as a later mover. */
+ level_setpiece(plevel, x, y, explosiontype(bp));
+
+ mover_consider(plevel, pmover->x, pmover->y, pmover->direction);
+
+ explode_sides(plevel, x, y, bp, ed);
+
+ break;
+ }
+ }
+
+ pmover = pmover->next;
+ }
+
+ /* Create the side explosions at the end, rather than during the previous
+ loop. This allows multiple explosions to occur in parallel. Centre
+ explosions will have already been created earlier on. */
+ pmover = plevel->mover_first;
+ while(pmover != NULL)
+ {
+ if(isnewexplosion(pmover->piece))
+ {
+ if(!isnewexplosion(level_piece(plevel, pmover->x, pmover->y)))
+ {
+ level_setprevious(plevel, pmover->x, pmover->y, level_piece(plevel, pmover->x, pmover->y));
+ level_setpreviousmoving(plevel, pmover->x, pmover->y, level_moving(plevel, pmover->x, pmover->y));
+ }
+
+ /* Use PIECE_EXPLOSION_NEW to allow detection of overlapping
+ explosions further down. */
+ level_setpiece(plevel, pmover->x, pmover->y, pmover->piece);
+ level_setmoving(plevel, pmover->x, pmover->y, pmover->direction);
+
+ pmover->piece += PIECE_EXPLOSION_FIRST - PIECE_EXPLOSION_NEW_FIRST;
+ }
+
+ pmover = pmover->next;
+ }
+
+ pmover = plevel->mover_first;
+ while(pmover != NULL)
+ {
+ if(isexplosion(pmover->piece))
+ {
+ /* Remove any explosions that overlap other explosions */
+ if(isexplosion(level_piece(plevel, pmover->x, pmover->y)))
+ pmover->piece = PIECE_GONE;
+ /* Otherwise, convert new explosions into explosions proper */
+ else
+ level_setpiece(plevel, pmover->x, pmover->y, pmover->piece);
+ }
+ /* Remove any movers that have exploded, or aren't as they should be */
+ if(level_piece(plevel, pmover->x, pmover->y) != pmover->piece)
+ {
+ pmover->piece = PIECE_GONE;
+ }
+ pmover = pmover->next;
+ }
+
+ /* Is player one still alive? */
+ if(level_piece(plevel, plevel->player_x[0], plevel->player_y[0]) != PIECE_PLAYER_ONE)
+ {
+ plevel->flags |= LEVELFLAG_MOVES;
+ plevel->alive[0] = 0;
+ }
+
+ /* Is player two still alive? */
+ if(level_piece(plevel, plevel->player_x[1], plevel->player_y[1]) != PIECE_PLAYER_TWO)
+ {
+ plevel->flags |= LEVELFLAG_MOVES;
+ plevel->alive[1] = 0;
+ }
+
+ /* Free old movers */
+ while(poldmovers != NULL)
+ {
+ pmover = poldmovers;
+ poldmovers = poldmovers->next;
+ free(pmover);
+ }
+
+ return 0;
+}
+
+void mover_consider(struct level* plevel, int x, int y, int d)
+{
+ int tx, ty;
+ int ad;
+
+ /* Is there already a mover in this space? If so, don't allow another */
+ if(level_moving(plevel, x, y) != MOVE_NONE)
+ return;
+
+#ifdef ENIGMA_COMPATIBILITY
+ /* Enigma doesn't consider the direction in which a space was emptied */
+ if(plevel->mode == MODE_ENIGMA)
+ {
+ mover_new(plevel, x, y, d, PIECE_SPACE, 1);
+ return;
+ }
+#endif
+
+#ifdef XOR_COMPATIBILITY
+ if(plevel->mode == MODE_XOR)
+ {
+ mover_new(plevel, x, y, d, PIECE_SPACE, 1);
+ return;
+ }
+#endif
+
+ ad = (d + 2) % 4;
+ tx = x + move_x[ad];
+ ty = y + move_y[ad];
+
+ /* Can a piece follow in the trail of this one? */
+ if(canfall(level_piece(plevel, tx, ty), PIECE_SPACE, d))
+ {
+ /* If it's moving already, just clear this space (1.07) */
+ if(level_moving(plevel, tx, ty) != MOVE_NONE)
+ {
+ mover_new(plevel, x, y, MOVE_NONE, PIECE_SPACE, 0);
+ return;
+ }
+
+ /* Otherwise, set it moving */
+ mover_new(plevel, x, y, d, level_piece(plevel, tx, ty), 1);
+ /* and see if there's anything following in its trail */
+ mover_consider(plevel, tx, ty, d);
+ return;
+ }
+
+ mover_new(plevel, x, y, d, PIECE_SPACE, 1);
+}
+
+void explode_sides(struct level* plevel, int x, int y, int p, int d)
+{
+ /* Chroma is subtle. This may be too subtle to have any effect in practice,
+ but the principle elsewhere is that things should be rotationally
+ symmetric, and this carries through here. */
+ if(plevel->mode == MODE_CHROMA)
+ {
+ switch(p % 4)
+ {
+ case 0: /* left */
+ mover_explode(plevel, x, y - 1, d, explosiontype(p) - 1);
+ mover_explode(plevel, x, y + 1, d, explosiontype(p) + 1);
+ break;
+
+ case 1: /* up */
+ mover_explode(plevel, x + 1, y, d, explosiontype(p) + 1);
+ mover_explode(plevel, x - 1, y, d, explosiontype(p) - 1);
+ break;
+
+ case 2: /* right */
+ mover_explode(plevel, x, y + 1, d, explosiontype(p) + 1);
+ mover_explode(plevel, x, y - 1, d, explosiontype(p) - 1);
+ break;
+
+ case 3: /* down */
+ mover_explode(plevel, x - 1, y, d, explosiontype(p) - 1);
+ mover_explode(plevel, x + 1, y, d, explosiontype(p) + 1);
+ break;
+ }
+ }
+ else
+ {
+ switch(p % 2)
+ {
+ case 0: /* left / right */
+ mover_explode(plevel, x, y - 1, d, explosiontype(p) - 1);
+ mover_explode(plevel, x, y + 1, d, explosiontype(p) + 1);
+ break;
+ case 1: /* up /down */
+ mover_explode(plevel, x - 1, y, d, explosiontype(p) - 1);
+ mover_explode(plevel, x + 1, y, d, explosiontype(p) + 1);
+ break;
+ }
+ }
+}
+
+
+int canfall(int p, int into, int d)
+{
+ /* Determine whether a piece can start moving */
+
+ /* Arrows and bombs */
+ if(p >= PIECE_MOVERS_FIRST && p<= PIECE_MOVERS_LAST)
+ {
+ /* can start falling in their natural direction */
+ if(d == (p % 4))
+ {
+ /* but only into empty space */
+ if(into == PIECE_SPACE)
+ return 1;
+#ifdef XOR_COMPATIBILITY
+ /* or into directional dots if appropriate */
+ if(into == PIECE_DOTS_X && (d == MOVE_LEFT || d == MOVE_RIGHT ))
+ return 1;
+ if(into == PIECE_DOTS_Y && (d == MOVE_UP || d == MOVE_DOWN ))
+ return 1;
+#endif
+ }
+ }
+
+ return 0;
+}
+
+
+int canmove(int p, int into, int d, int fast)
+{
+ /* Determine whether a piece can continue moving */
+
+ /* Arrows and bombs */
+ if(p >= PIECE_MOVERS_FIRST && p<= PIECE_MOVERS_LAST)
+ {
+ /* can continue moving in their natural direction */
+ if(d == (p % 4))
+ {
+ /* into empty space */
+ if(into == PIECE_SPACE)
+ return 1;
+ /* into dots if they're already moving */
+ if(into == PIECE_DOTS && fast)
+ return 1;
+#ifdef XOR_COMPATIBILITY
+ /* into directional dots if appropriate */
+ if(into == PIECE_DOTS_X && (d == MOVE_LEFT || d == MOVE_RIGHT ))
+ return 1;
+ if(into == PIECE_DOTS_Y && (d == MOVE_UP || d == MOVE_DOWN ))
+ return 1;
+#endif
+ /* through dying explosions */
+ if(isexplosion(into))
+ return 1;
+ /* can kill players if already moving */
+ if(into == PIECE_PLAYER_ONE && fast)
+ return 1;
+ if(into == PIECE_PLAYER_TWO && fast)
+ return 1;
+ }
+ return 0;
+ }
+
+ /* Circles */
+ if(p == PIECE_CIRCLE)
+ {
+ /* are stopped by everything other than empty space */
+ if(into == PIECE_SPACE)
+ return 1;
+ /* and dying explosions */
+ if(isexplosion(into))
+ return 1;
+ return 0;
+ }
+
+ return 0;
+}
+
+int canbepushed(int p, int into, int d, int mode)
+{
+ /* Determine whether a piece can be pushed by the player */
+
+ /* Arrows and bombs */
+ if(p >= PIECE_MOVERS_FIRST && p<= PIECE_MOVERS_LAST)
+ {
+ /* can be pushed, but not against their natural direction */
+ if(d != ((p + 2) % 4))
+ {
+ /* into empty space or through dots */
+ if(into == PIECE_SPACE || into == PIECE_DOTS)
+ return 1;
+#ifdef XOR_COMPATIBILITY
+ /* through directional dots if appropriate */
+ if(into == PIECE_DOTS_X && (d == MOVE_LEFT || d == MOVE_RIGHT))
+ return 1;
+ if(into == PIECE_DOTS_Y && (d == MOVE_UP || d == MOVE_DOWN))
+ return 1;
+#endif
+ }
+ return 0;
+ }
+
+ /* Circles can be pushed in any direction */
+ if(p == PIECE_CIRCLE
+#ifdef ENIGMA_COMPATIBILITY
+ || p == PIECE_CIRCLE_DOUBLE
+#endif
+ )
+ {
+ /* into empty space */
+ if(into == PIECE_SPACE)
+ return 1;
+#ifdef XOR_COMPATIBILITY
+ /* XOR won't let circles (dolls) pass through dots */
+ if(mode == MODE_XOR)
+ return 0;
+#endif
+ /* pushed through dots */
+ if(into == PIECE_DOTS)
+ return 1;
+ return 0;
+ }
+
+ return 0;
+}
+
+int canexplode(int p, int i, int d, int fast, int mode)
+{
+ /* Only an already moving arrow or bomb can act as a detonator */
+ if(fast == 0)
+ return 0;
+
+ /* Arrows can detonate bombs */
+ if(p >= PIECE_ARROW_RED_LEFT && p<= PIECE_ARROW_RED_DOWN &&
+ i >= PIECE_BOMB_RED_LEFT && i<= PIECE_BOMB_RED_DOWN)
+ return 1;
+ if(p >= PIECE_ARROW_GREEN_LEFT && p<= PIECE_ARROW_GREEN_DOWN &&
+ i >= PIECE_BOMB_GREEN_LEFT && i<= PIECE_BOMB_GREEN_DOWN)
+ return 1;
+ if(p >= PIECE_ARROW_BLUE_LEFT && p<= PIECE_ARROW_BLUE_DOWN &&
+ i >= PIECE_BOMB_BLUE_LEFT && i<= PIECE_BOMB_BLUE_DOWN)
+ return 1;
+
+#ifdef ENIGMA_COMPATIBILITY
+ /* Enigma requires a moving arrow to detonate a stationary bomb, and
+ does not permit bombs to detonate other bombs */
+ if(mode == MODE_ENIGMA)
+ return 0;
+#endif
+
+ /* Bombs can be detonated by arrows pointing towards them */
+ if(p >= PIECE_BOMB_RED_LEFT && p<= PIECE_BOMB_RED_DOWN &&
+ i == (PIECE_ARROW_RED_LEFT + ((d + 2) % 4)))
+ return 1;
+ if(p >= PIECE_BOMB_GREEN_LEFT && p<= PIECE_BOMB_GREEN_DOWN &&
+ i == (PIECE_ARROW_GREEN_LEFT + ((d + 2) % 4)))
+ return 1;
+ if(p >= PIECE_BOMB_BLUE_LEFT && p<= PIECE_BOMB_BLUE_DOWN &&
+ i == (PIECE_ARROW_BLUE_LEFT + ((d + 2) % 4)))
+ return 1;
+
+ /* Bombs can detonate other bombs */
+ if(p >= PIECE_BOMB_RED_LEFT && p<= PIECE_BOMB_RED_DOWN &&
+ i >= PIECE_BOMB_RED_LEFT && i<= PIECE_BOMB_RED_DOWN)
+ return 1;
+ if(p >= PIECE_BOMB_GREEN_LEFT && p<= PIECE_BOMB_GREEN_DOWN &&
+ i >= PIECE_BOMB_GREEN_LEFT && i<= PIECE_BOMB_GREEN_DOWN)
+ return 1;
+ if(p >= PIECE_BOMB_BLUE_LEFT && p<= PIECE_BOMB_BLUE_DOWN &&
+ i >= PIECE_BOMB_BLUE_LEFT && i<= PIECE_BOMB_BLUE_DOWN)
+ return 1;
+
+ return 0;
+}
+
+struct mover* mover_newundo(struct level* plevel, int x, int y, int d, int piece, int previous, int flags)
+{
+ struct mover* pmover;
+
+ static int count = 0;
+
+ if(plevel->flags & LEVELFLAG_NOUNDO)
+ return NULL;
+
+ pmover = (struct mover*)malloc(sizeof(struct mover));
+ if(pmover == NULL)
+ fatal("Out of memory in mover_newundo()");
+
+ pmover->x = x;
+ pmover->y = y;
+ pmover->direction = d;
+ pmover->piece = piece;
+ pmover->piece_previous = previous;
+ pmover->next = NULL;
+ pmover->previous = plevel->mover_last;
+
+ if(flags & MOVER_FAST)
+ pmover->fast = 1;
+ else
+ pmover->fast = 0;
+
+ if(flags & MOVER_UNDO)
+ {
+ level_setmoving(plevel, pmover->x, pmover->y, pmover->direction);
+
+ if(options_debug & DEBUG_MOVERS)
+ {
+ if(plevel->mover_first == NULL)
+ count = 0;
+
+ fprintf(stderr, "[%d] Cosmetic mover at (%d,%d) is %s was %s (direction=%c) (flags=%d)\n",
+ count ++, pmover->x, pmover->y,
+ piece_name[pmover->piece], piece_name[pmover->piece_previous],
+ directiontochar(pmover->direction), pmover->fast);
+ }
+
+ if(plevel->mover_first == NULL)
+ plevel->mover_first = pmover;
+
+ if(plevel->mover_last != NULL)
+ plevel->mover_last->next = pmover;
+
+ plevel->mover_last = pmover;
+
+ }
+
+ if(flags & MOVER_STORE)
+ {
+ pmover->previous = plevel->move_current->mover_last;
+ pmover->next = NULL;
+
+ if(plevel->move_current->mover_first == NULL)
+ plevel->move_current->mover_first = pmover;
+ if(plevel->move_current->mover_last != NULL)
+ plevel->move_current->mover_last->next = pmover;
+ plevel->move_current->mover_last = pmover;
+
+ }
+
+ return pmover;
+}
+
+
+int level_undo(struct level* plevel)
+{
+ struct mover* pmover;
+ struct mover* ptmp;
+ struct mover* pmoverfirst;
+
+ int d, td;
+
+ int count = 0;
+
+ /* Can't undo if the level has no undo data (eg, a partial save) */
+ if(plevel->move_first == NULL || (plevel->move_first->mover_first == NULL && plevel->move_current != plevel->move_first))
+ return 0;
+
+ /* Working backwards, undo any changes made to the map by movers in the
+ previous step. */
+ pmoverfirst = NULL;
+ pmover = plevel->mover_first;
+ while(pmover != NULL)
+ {
+ pmoverfirst = pmover;
+ pmover = pmover->next;
+ }
+ pmover = pmoverfirst;
+ while(pmover != NULL)
+ {
+ /* Not setting SPACEs fixes a pathological case without apparently breaking anything (1.07) */
+ if(pmover->piece != PIECE_SPACE)
+ {
+ level_setpiece(plevel, pmover->x, pmover->y, pmover->piece);
+ if(options_debug & DEBUG_MOVERS)
+ fprintf(stderr, "+ level_setpiece(%d, %d, %s)\n", pmover->x, pmover->y, piece_name[pmover->piece]);
+ }
+
+ pmover = pmover->previous;
+ if(pmover == NULL)
+ break;
+ }
+
+ /* Is player one still alive? */
+ if(level_piece(plevel, plevel->player_x[0], plevel->player_y[0]) != PIECE_PLAYER_ONE)
+ plevel->alive[0] = 0;
+ /* Is player two still alive? */
+ if(level_piece(plevel, plevel->player_x[1], plevel->player_y[1]) != PIECE_PLAYER_TWO)
+ plevel->alive[1] = 0;
+
+ /* Tidy up any movers created in the previous step */
+ pmover = plevel->mover_first;
+ while(pmover != NULL)
+ {
+ level_setmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
+ level_setprevious(plevel, pmover->x, pmover->y, PIECE_SPACE);
+ ptmp = pmover;
+ pmover = pmover->next;
+ free(ptmp);
+ }
+ plevel->mover_first = NULL;
+ plevel->mover_last = NULL;
+
+ /* Can't undo at very start of level */
+ if(plevel->move_current == NULL)
+ return 0;
+
+ /* If there is no previous step to undo, remove this move entirely */
+ if(plevel->move_current->mover_last == NULL)
+ {
+ plevel->move_current = plevel->move_current->previous;
+ plevel->flags |= LEVELFLAG_MOVES;
+
+ return 0;
+ }
+
+ if(options_debug & DEBUG_MOVERS)
+ fprintf(stderr, "\n");
+
+ /* Start from the last mover for this step. */
+ pmover = plevel->move_current->mover_last;
+
+ pmoverfirst = NULL;
+
+ td = MOVE_NONE;
+
+ /* Working backwards, remove these pieces from the map */
+ while(pmover != NULL)
+ {
+ pmoverfirst = pmover;
+
+ level_setpiece(plevel, pmover->x, pmover->y, PIECE_SPACE);
+
+ if(options_debug & DEBUG_MOVERS)
+ fprintf(stderr, "- level_setpiece(%d, %d, %s)\n", pmover->x, pmover->y, piece_name[PIECE_SPACE]);
+
+ /* If the piece is the player, update position and status */
+ if(pmover->piece_previous == PIECE_PLAYER_ONE || pmover->piece_previous == PIECE_PLAYER_TWO)
+ {
+ plevel->player_x[pmover->piece_previous - PIECE_PLAYER_ONE] = pmover->x;
+ plevel->player_y[pmover->piece_previous - PIECE_PLAYER_ONE] = pmover->y;
+
+#ifdef XOR_COMPATIBILITY
+ if(plevel->mode == MODE_XOR)
+ {
+ /* If a player is being resurrected in this move, and the
+ other player is alive, undo the automatic swap */
+ if(plevel->alive[pmover->piece_previous - PIECE_PLAYER_ONE] == 0 && plevel->alive[plevel->player])
+ {
+ /* Cosmetic mover to deactivate other player */
+ mover_newundo(plevel, plevel->player_x[plevel->player], plevel->player_y[plevel->player], MOVE_SWAPPED, PIECE_PLAYER_ONE + plevel->player, PIECE_SPACE, MOVER_UNDO);
+ plevel->player = pmover->piece_previous - PIECE_PLAYER_ONE;
+ }
+
+ /* The active player is the one which moves first
+ (last in undo */
+ plevel->player = pmover->piece_previous - PIECE_PLAYER_ONE;
+ }
+#endif
+
+ plevel->alive[pmover->piece_previous - PIECE_PLAYER_ONE] = 1;
+ }
+
+#ifdef XOR_COMPATIBILITY
+ /* If the piece is a teleport, store the direction of the original move
+ into it for later use. */
+ if(pmover->piece == PIECE_TELEPORT)
+ td = pmover->direction;
+#endif
+
+ /* until we reach the first mover for this step. */
+ pmover = pmover->previous;
+ if(pmover != NULL && pmover->fast == 0)
+ break;
+ }
+
+ pmover = pmoverfirst;
+
+ /* Now, move forwards through the movers and create cosmetic effects. */
+ while(pmover != NULL)
+ {
+
+ if(options_debug & DEBUG_MOVERS)
+ fprintf(stderr, "[%d] Undo mover at (%d,%d) is %s was %s (direction=%c) (flags=%d)\n",
+ count++, pmover->x, pmover->y,
+ piece_name[pmover->piece], piece_name[pmover->piece_previous],
+ directiontochar(pmover->direction), pmover->fast);
+
+ d = pmover->direction;
+
+ if(d != MOVE_NONE && d != MOVE_SWAP && d != MOVE_SWAPPED)
+ d = (d + 2) % 4;
+
+ if(isexplosion(pmover->piece))
+ {
+ /* Explosions don't move. */
+ d = MOVE_NONE;
+ /* Show dying explosion when undoing new explosion */
+ if(options_debug & DEBUG_MOVERS)
+ fprintf(stderr, "* level_setprevious(%d, %d, %s)\n", pmover->x, pmover->y, piece_name[pmover->piece]);
+ level_setprevious(plevel, pmover->x, pmover->y, pmover->piece);
+ }
+
+ /* Do we need to patch up the direction this piece is moving in? */
+ /* Is it the player? */
+ if((pmover->piece_previous == PIECE_PLAYER_ONE || pmover->piece_previous == PIECE_PLAYER_TWO) && (pmover->piece == PIECE_SPACE || pmover->piece == PIECE_GONE))
+ {
+ /* If so, are they moving out of a teleport? Use original direction
+ of move if so. */
+ if(td != MOVE_NONE)
+ d = (td + 2) % 4;
+ }
+ /* Otherwise, if the previous piece wasn't a move, it must have been a
+ static piece being eaten by a mover, and thus shouldn't move. */
+ else if((pmover->piece_previous < PIECE_MOVERS_FIRST || pmover->piece_previous > PIECE_MOVERS_LAST) && pmover->piece_previous != PIECE_CIRCLE
+#ifdef ENIGMA_COMPATIBILITY
+ && pmover->piece_previous != PIECE_CIRCLE_DOUBLE
+#endif
+ )
+ d = MOVE_NONE;
+
+ /* Plot a cosmetic mover. */
+ if(level_previous(plevel, pmover->x, pmover->y) != PIECE_SPACE)
+ d = MOVE_NONE;
+ /* but not if there are overlapping explosions */
+ if(!(pmover->piece_previous >= PIECE_EXPLOSION_NEW_FIRST && pmover->piece_previous <= PIECE_EXPLOSION_NEW_LAST))
+ mover_newundo(plevel, pmover->x, pmover->y, d, pmover->piece_previous, PIECE_SPACE, MOVER_UNDO);
+
+ pmover = pmover->next;
+ }
+
+ pmover = pmoverfirst->previous;
+
+ /* If there is another step, set it up for the next iteration */
+ if(pmover != NULL)
+ {
+ plevel->move_current->mover_last = pmover;
+ pmover = pmover->next;
+ }
+ else
+ {
+ pmover = plevel->move_current->mover_first;
+ plevel->move_current->mover_first = NULL;
+ plevel->move_current->mover_last = NULL;
+
+ plevel->moves --;
+ }
+
+ /* Remove the movers in the step we've just done */
+ if(pmover != NULL)
+ {
+ while(pmover != NULL)
+ {
+ /* Undo any pieces exploded or caught */
+ if(pmover->piece_previous == PIECE_STAR)
+ {
+ if(pmover->piece == PIECE_PLAYER_ONE || pmover->piece == PIECE_PLAYER_TWO)
+ plevel->stars_caught --;
+ else
+ plevel->stars_exploded --;
+
+ plevel->flags |= LEVELFLAG_STARS;
+ }
+#ifdef XOR_COMPATIBILITY
+ if(pmover->piece_previous == PIECE_SWITCH)
+ {
+ plevel->switched = 1 - plevel->switched;
+ plevel->flags |= LEVELFLAG_SWITCH;
+ }
+ if(pmover->piece_previous == PIECE_MAP_TOP_LEFT)
+ {
+ plevel->mapped ^= MAPPED_TOP_LEFT;
+ plevel->flags |= LEVELFLAG_MAP;
+ }
+ if(pmover->piece_previous == PIECE_MAP_TOP_RIGHT)
+ {
+ plevel->mapped ^= MAPPED_TOP_RIGHT;
+ plevel->flags |= LEVELFLAG_MAP;
+ }
+ if(pmover->piece_previous == PIECE_MAP_BOTTOM_LEFT)
+ {
+ plevel->mapped ^= MAPPED_BOTTOM_LEFT;
+ plevel->flags |= LEVELFLAG_MAP;
+ }
+ if(pmover->piece_previous == PIECE_MAP_BOTTOM_RIGHT)
+ {
+ plevel->mapped ^= MAPPED_BOTTOM_RIGHT;
+ plevel->flags |= LEVELFLAG_MAP;
+ }
+#endif
+
+ ptmp = pmover;
+ pmover = pmover->next;
+ free(ptmp);
+ }
+ }
+
+ if(plevel->move_current->mover_last != NULL)
+ plevel->move_current->mover_last->next = NULL;
+
+ /* If the move was a swap, revert to the previous player */
+ if(plevel->move_current->direction == MOVE_SWAP)
+ plevel->player = 1 - plevel->player;
+
+ /* Have we just undone failure? */
+ if((plevel->flags & LEVELFLAG_FAILED) && (plevel->alive[0] != 0 || plevel->alive[1] != 0))
+ {
+ plevel->flags &= ~LEVELFLAG_FAILED;
+ plevel->flags |= LEVELFLAG_MOVES;
+ }
+
+ /* Have we just undone success? */
+ if(plevel->flags & (LEVELFLAG_SOLVED | LEVELFLAG_EXIT))
+ {
+ plevel->flags &= ~LEVELFLAG_SOLVED;
+ plevel->flags &= ~LEVELFLAG_EXIT;
+ plevel->flags |= LEVELFLAG_STARS;
+ }
+
+#ifdef XOR_COMPATIBILITY
+ if(plevel->mode == MODE_XOR)
+ xor_focus(plevel);
+#endif
+
+ /* If there are no more steps in this move, chroma-curses needs advanced
+ warning that the move counter is going to change. */
+ if(plevel->move_current->mover_last == NULL)
+ {
+ plevel->flags |= LEVELFLAG_MOVES;
+ }
+
+ return 1;
+}