1 /*
2 engine.c
3
4 Copyright (C) 2010-2019 Amf
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23
24 #include "chroma.h"
25 #include "level.h"
26 #include "util.h"
27
28 #ifdef XOR_COMPATIBILITY
29 int xor_move(struct level* plevel, int move);
30 int xor_evolve(struct level* plevel);
31 void xor_focus(struct level* plevel);
32 extern int options_xor_mode;
33 #endif
34 #ifdef ENIGMA_COMPATIBILITY
35 int enigma_move(struct level* plevel, int move);
36 int enigma_evolve(struct level* plevel);
37 extern int options_enigma_mode;
38 #endif
39
40 extern int options_debug;
41 extern char* piece_name[];
42
43 /* l u r d n s w */
44 int move_x[] = {-1, 0, 1, 0, 0, 0, 0};
45 int move_y[] = {0, -1, 0, 1, 0, 0, 0};
46
47 #ifdef ENIGMA_COMPATIBILITY
48 int enigma_move_order[] = {MOVE_DOWN, MOVE_RIGHT, MOVE_LEFT, MOVE_UP};
49 #endif
50 #ifdef XOR_COMPATIBILITY
51 int xor_teleport_order[] = {MOVE_RIGHT, MOVE_UP, MOVE_LEFT, MOVE_DOWN};
52 #endif
53
54 struct mover* mover_new(struct level* plevel, int x, int y, int d, int piece, int fast);
55 void mover_consider(struct level* plevel, int x, int y, int d);
56 struct mover* mover_explode(struct level *plevel, int x, int y, int d, int p);
57 void explode_sides(struct level* plevel, int x, int y, int p, int d);
58 int canfall(int p, int into, int d);
59 int canmove(int p, int into, int d, int fast);
60 int canexplode(int p, int into, int d, int fast, int mode);
61 int canbepushed(int p, int into, int d, int mode);
62
explosiontype(int p)63 int explosiontype(int p)
64 {
65 switch(p)
66 {
67 case PIECE_ARROW_RED_LEFT:
68 case PIECE_ARROW_RED_RIGHT:
69 case PIECE_BOMB_RED_LEFT:
70 case PIECE_BOMB_RED_RIGHT:
71 return PIECE_EXPLOSION_NEW_RED_VERTICAL;
72 case PIECE_ARROW_RED_UP:
73 case PIECE_ARROW_RED_DOWN:
74 case PIECE_BOMB_RED_UP:
75 case PIECE_BOMB_RED_DOWN:
76 return PIECE_EXPLOSION_NEW_RED_HORIZONTAL;
77
78 case PIECE_ARROW_GREEN_LEFT:
79 case PIECE_ARROW_GREEN_RIGHT:
80 case PIECE_BOMB_GREEN_LEFT:
81 case PIECE_BOMB_GREEN_RIGHT:
82 return PIECE_EXPLOSION_NEW_GREEN_VERTICAL;
83 case PIECE_ARROW_GREEN_UP:
84 case PIECE_ARROW_GREEN_DOWN:
85 case PIECE_BOMB_GREEN_UP:
86 case PIECE_BOMB_GREEN_DOWN:
87 return PIECE_EXPLOSION_NEW_GREEN_HORIZONTAL;
88
89 case PIECE_ARROW_BLUE_LEFT:
90 case PIECE_ARROW_BLUE_RIGHT:
91 case PIECE_BOMB_BLUE_LEFT:
92 case PIECE_BOMB_BLUE_RIGHT:
93 return PIECE_EXPLOSION_NEW_BLUE_VERTICAL;
94 case PIECE_ARROW_BLUE_UP:
95 case PIECE_ARROW_BLUE_DOWN:
96 case PIECE_BOMB_BLUE_UP:
97 case PIECE_BOMB_BLUE_DOWN:
98 return PIECE_EXPLOSION_NEW_BLUE_HORIZONTAL;
99
100 default:
101 /* This should never happen */
102 return PIECE_GONE;
103 }
104 }
105
level_moved(struct level * plevel,int move)106 void level_moved(struct level* plevel, int move)
107 {
108 if(move != MOVE_REDO)
109 level_addmove(plevel, move);
110 else
111 {
112 if(plevel->move_current != NULL)
113 plevel->move_current = plevel->move_current->next;
114 else
115 plevel->move_current = plevel->move_first;
116 }
117
118 plevel->moves ++;
119 plevel->flags |= LEVELFLAG_MOVES;
120
121 level_storemovers(plevel);
122 }
123
level_move(struct level * plevel,int move)124 int level_move(struct level* plevel, int move)
125 {
126 int x, y;
127 int tx, ty;
128 int px, py;
129 int p;
130 int ok;
131 #ifdef XOR_COMPATIBILITY
132 int dx, dy;
133 int teleport;
134 int td;
135 int i;
136 #endif
137 int realmove;
138 struct move* pmove;
139
140 realmove = move;
141
142 if(plevel == NULL || plevel->mover_first != NULL)
143 return 0;
144
145 if(move == MOVE_REDO)
146 {
147 if(plevel->move_current == NULL)
148 pmove = plevel->move_first;
149 else
150 pmove = plevel->move_current->next;
151
152 if(pmove == NULL)
153 return 0;
154
155 move = pmove->direction;
156 }
157
158 if(move == MOVE_SWAP)
159 {
160 if(plevel->alive[1 - plevel->player])
161 {
162 plevel->player = 1 - plevel->player;
163
164 /* Create new movers for the stationary swapped players to allow
165 the display to redraw them after the swap. */
166 mover_new(plevel, plevel->player_x[plevel->player], plevel->player_y[plevel->player], MOVE_SWAP, PIECE_PLAYER_ONE + plevel->player, 0);
167
168 /* Is the first player still alive? */
169 if(plevel->alive[1 - plevel->player])
170 mover_new(plevel, plevel->player_x[1 - plevel->player], plevel->player_y[1 - plevel->player], MOVE_SWAPPED, PIECE_PLAYER_ONE + 1 - plevel->player, 0);
171
172 level_moved(plevel, realmove);
173
174 return 1;
175 }
176 return 0;
177 }
178
179 if(plevel->alive[plevel->player] == 0)
180 return 0;
181
182 #ifdef XOR_COMPATIBILITY
183 if(plevel->mode == MODE_XOR && options_xor_mode == 1)
184 {
185 if(xor_move(plevel, move))
186 {
187 level_moved(plevel, realmove);
188 xor_focus(plevel);
189 return 1;
190 }
191 return 0;
192 }
193 #endif
194 #ifdef ENIGMA_COMPATIBILITY
195 if(plevel->mode == MODE_ENIGMA && options_enigma_mode == 1)
196 {
197 if(enigma_move(plevel, move))
198 {
199 level_moved(plevel, realmove);
200 return 1;
201 }
202 return 0;
203 }
204
205 #endif
206
207 /* Consider where we are moving to */
208 x = plevel->player_x[plevel->player] + move_x[move];
209 y = plevel->player_y[plevel->player] + move_y[move];
210
211 p = level_piece(plevel, x, y);
212
213 ok = 0;
214
215 /* Can we move into the piece in that direction? */
216 switch(p)
217 {
218 case PIECE_DOOR:
219 if(plevel->stars_caught == plevel->stars_total)
220 {
221 plevel->flags |= LEVELFLAG_EXIT;
222 ok = 1;
223 }
224 break;
225
226 case PIECE_STAR:
227 plevel->stars_caught ++;
228 plevel->flags |= LEVELFLAG_STARS;
229 ok = 1;
230 break;
231
232 #ifdef XOR_COMPATIBILITY
233 case PIECE_TELEPORT:
234 /* Only XOR has teleports. We force the issue so as not to break
235 Chroma's rotational symmetry by introducing teleport order. */
236 if(plevel->mode != MODE_XOR)
237 break;
238
239 teleport = -1;
240 if(x == plevel->teleport_x[0] && y == plevel->teleport_y[0])
241 teleport = 0;
242 if(x == plevel->teleport_x[1] && y == plevel->teleport_y[1])
243 teleport = 1;
244 if(teleport != -1)
245 {
246 tx = plevel->teleport_x[1 - teleport];
247 ty = plevel->teleport_y[1 - teleport];
248 td = move;
249
250 /* Does the other teleport still exist? */
251 if(level_piece(plevel, tx, ty) == PIECE_TELEPORT)
252 {
253 ok = 0;
254 /* Find the first available exit from it */
255 for(i = 0; i < 4; i ++)
256 {
257 dx = tx + move_x[xor_teleport_order[i]];
258 dy = ty + move_y[xor_teleport_order[i]];
259 if(!ok && level_piece(plevel, dx, dy) == PIECE_SPACE)
260 {
261 /* Change move to produce the effect of coming
262 out of the teleport */
263 x = dx; y = dy; move = xor_teleport_order[i];
264 ok = 1;
265 }
266 }
267
268 if(ok)
269 {
270 /* Visual effects for the player going in one teleport */
271 /* Store original player move direction in cosmetic mover */
272 mover_new(plevel, plevel->teleport_x[teleport], plevel->teleport_y[teleport], td, PIECE_TELEPORT, 0);
273 level_setprevious(plevel, plevel->teleport_x[teleport], plevel->teleport_y[teleport], PIECE_PLAYER_ONE + plevel->player);
274 level_setpreviousmoving(plevel, plevel->teleport_x[teleport], plevel->teleport_y[teleport], realmove);
275 /* and out of the other teleport */
276 mover_new(plevel, plevel->teleport_x[1 - teleport], plevel->teleport_y[1 - teleport], MOVE_NONE, PIECE_TELEPORT, 0);
277
278 /* Change the viewpoint to that of the other teleport */
279 plevel->view_x[plevel->player] = plevel->view_teleport_x[1 - teleport];
280 plevel->view_y[plevel->player] = plevel->view_teleport_y[1 - teleport];
281
282 }
283 }
284 }
285 break;
286
287 case PIECE_SWITCH:
288 plevel->switched = 1 - plevel->switched;
289 plevel->flags |= LEVELFLAG_SWITCH;
290 ok = 1;
291 break;
292
293 case PIECE_MAP_TOP_LEFT:
294 plevel->mapped |= MAPPED_TOP_LEFT;
295 plevel->flags |= LEVELFLAG_MAP;
296 ok = 1;
297 break;
298 case PIECE_MAP_TOP_RIGHT:
299 plevel->mapped |= MAPPED_TOP_RIGHT;
300 plevel->flags |= LEVELFLAG_MAP;
301 ok = 1;
302 break;
303 case PIECE_MAP_BOTTOM_LEFT:
304 plevel->mapped |= MAPPED_BOTTOM_LEFT;
305 plevel->flags |= LEVELFLAG_MAP;
306 ok = 1;
307 break;
308 case PIECE_MAP_BOTTOM_RIGHT:
309 plevel->mapped |= MAPPED_BOTTOM_RIGHT;
310 plevel->flags |= LEVELFLAG_MAP;
311 ok = 1;
312 break;
313
314 case PIECE_DOTS_X:
315 if(move == MOVE_LEFT || move == MOVE_RIGHT)
316 ok = 1;
317 break;
318
319 case PIECE_DOTS_Y:
320 if(move == MOVE_UP || move == MOVE_DOWN)
321 ok = 1;
322 break;
323 #endif
324
325 #ifdef ENIGMA_COMPATIBILITY
326 case PIECE_DOTS_DOUBLE:
327 #endif
328 case PIECE_DOTS:
329 case PIECE_SPACE:
330 ok = 1;
331 break;
332 }
333
334 /* Is there a piece we can push? */
335 if(!ok)
336 {
337 tx = x + move_x[move];
338 ty = y + move_y[move];
339
340 if(canbepushed(p, level_piece(plevel, tx, ty), move, plevel->mode))
341 {
342 mover_new(plevel, tx, ty, move, p, 0);
343 ok = 1;
344 }
345 }
346
347 if(ok)
348 {
349
350 /* Cosmetic mover for storing the player's direction in undo */
351 mover_new(plevel, plevel->player_x[plevel->player], plevel->player_y[plevel->player], move, PIECE_GONE, 0);
352
353 mover_new(plevel, x, y, move, PIECE_PLAYER_ONE + plevel->player, 0);
354
355 px = plevel->player_x[plevel->player];
356 py = plevel->player_y[plevel->player];
357
358 #ifdef XOR_COMPATIBILITY
359 /* XOR protects the players move */
360 if(plevel->mode == MODE_XOR)
361 {
362 /* Blank the player's space first to avoid upsetting undo */
363 level_setpiece(plevel, px, py, PIECE_SPACE);
364 mover_new(plevel, px, py, (move + 1) % 4, PIECE_SPACE, 1);
365 }
366 /* Chroma lets a piece follow in the player's trail */
367 else
368 {
369 #endif
370 /* Blank the player's space first to avoid upsetting undo */
371 level_setpiece(plevel, px, py, PIECE_SPACE);
372 mover_consider(plevel, px, py, move % 4);
373 #ifdef XOR_COMPATIBILITY
374 }
375 #endif
376
377 plevel->player_x[plevel->player] = x;
378 plevel->player_y[plevel->player] = y;
379
380 level_moved(plevel, realmove);
381
382 #ifdef XOR_COMPATIBILITY
383 if(plevel->mode == MODE_XOR)
384 xor_focus(plevel);
385 #endif
386
387 return 1;
388 }
389
390 return 0;
391 }
392
mover_explode(struct level * plevel,int x,int y,int d,int p)393 struct mover* mover_explode(struct level *plevel, int x, int y, int d, int p)
394 {
395 /* Don't explode any of the edge wall */
396 if(x == 0 || y == 0 || x == plevel->size_x - 1 || y == plevel->size_y - 1)
397 return NULL;
398
399 /* What have we exploded? */
400 switch(level_piece(plevel, x, y))
401 {
402 case PIECE_STAR:
403 plevel->stars_exploded ++;
404 plevel->flags |= LEVELFLAG_STARS;
405 break;
406
407 #ifdef XOR_COMPATIBILITY
408 case PIECE_SWITCH:
409 plevel->switched = 1 - plevel->switched;
410 plevel->flags |= LEVELFLAG_SWITCH;
411 break;
412 #endif
413 }
414
415 return mover_new(plevel, x, y, d, p, 1);
416 }
417
mover_new(struct level * plevel,int x,int y,int d,int piece,int fast)418 struct mover* mover_new(struct level* plevel, int x, int y, int d, int piece, int fast)
419 {
420 struct mover* pmover;
421 int previous;
422 int data;
423
424 /* Don't allow two movers in the same space, unless one is exploding */
425 if(!isnewexplosion(piece) && level_moving(plevel, x, y) != MOVE_NONE)
426 return NULL;
427
428 pmover = (struct mover*)malloc(sizeof(struct mover));
429 if(pmover == NULL)
430 fatal("Out of memory in mover_new()");
431
432 previous = level_piece(plevel, x, y);
433
434 pmover->x = x;
435 pmover->y = y;
436 pmover->direction = d;
437 pmover->piece = piece;
438 pmover->piece_previous = previous;
439 pmover->fast = fast;
440 pmover->next = NULL;
441 pmover->previous = plevel->mover_last;
442
443 if(plevel->mover_first == NULL)
444 plevel->mover_first = pmover;
445
446 if(plevel->mover_last != NULL)
447 plevel->mover_last->next = pmover;
448
449 plevel->mover_last = pmover;
450
451 /* Show pieces collected by players */
452 if(piece == PIECE_PLAYER_ONE || piece == PIECE_PLAYER_TWO)
453 {
454 if((previous < PIECE_MOVERS_FIRST || previous > PIECE_MOVERS_LAST)
455 && previous != PIECE_CIRCLE
456 #ifdef ENIGMA_COMPATIBILITY
457 && previous != PIECE_CIRCLE_DOUBLE
458 #endif
459 && previous != PIECE_PLAYER_ONE
460 && previous != PIECE_PLAYER_TWO)
461 level_setprevious(plevel, x, y, previous);
462 }
463
464 /* Show players squashed by movers */
465 if(piece >= PIECE_MOVERS_FIRST && piece <= PIECE_MOVERS_LAST)
466 {
467 if(previous == PIECE_PLAYER_ONE || previous == PIECE_PLAYER_TWO)
468 level_setprevious(plevel, x, y, previous);
469 }
470
471 /* Show pieces removed by movers or explosions */
472 if(previous == PIECE_DOTS
473 #ifdef ENIGMA_COMPATIBILITY
474 || previous == PIECE_DOTS_DOUBLE
475 #endif
476 #ifdef XOR_COMPATIBILITY
477 || previous == PIECE_DOTS_X
478 || previous == PIECE_DOTS_Y
479 #endif
480 || isexplosion(previous))
481 level_setprevious(plevel, x, y, previous);
482
483 /* Show exploded pieces */
484 if(isnewexplosion(piece) && !isnewexplosion(previous))
485 {
486 level_setprevious(plevel, x, y, previous);
487 level_setpreviousmoving(plevel, x, y, level_moving(plevel, x, y));
488 }
489
490 /* Explosions occur later */
491 if(!isnewexplosion(piece) && piece != PIECE_GONE)
492 {
493 level_setpiece(plevel, x, y, piece);
494 level_setmoving(plevel, x, y, d);
495 }
496
497 /* Maintain piece graphic */
498 if(d != MOVE_NONE)
499 {
500 data = level_data(plevel, x - move_x[d], y - move_y[d]) & 0xff00;
501 data = (level_data(plevel, x, y) & ~0xff00) | data;
502 level_setdata(plevel, x, y, data);
503 }
504
505 return pmover;
506 }
507
mover_addtostack(struct level * plevel,int x,int y,int move)508 struct mover* mover_addtostack(struct level* plevel, int x, int y, int move)
509 {
510 struct mover* pmover;
511
512 pmover = (struct mover*)malloc(sizeof(struct mover));
513 if(pmover == NULL)
514 fatal("Out of memory in mover_addtostack()");
515
516 pmover->x = x;
517 pmover->y = y;
518 pmover->direction = move;
519 pmover->piece = PIECE_SPACE;
520 pmover->fast = 0;
521 pmover->next = NULL;
522
523 if(plevel->stack_first == NULL)
524 plevel->stack_first = pmover;
525
526 if(plevel->stack_last != NULL)
527 plevel->stack_last->next = pmover;
528
529 plevel->stack_last = pmover;
530
531 return pmover;
532 }
533
level_storemovers(struct level * plevel)534 void level_storemovers(struct level* plevel)
535 {
536 struct mover* pmover;
537 int previous;
538
539 int count = 0;
540
541 if(plevel->move_current == NULL || plevel->mover_first == NULL)
542 return;
543
544 if((options_debug & DEBUG_MOVERS) && plevel->move_current->mover_first == NULL)
545 fprintf(stderr, "\n");
546
547 pmover = plevel->mover_first;
548 while(pmover != NULL)
549 {
550 /* If something is moving into an explosion, don't store it as the
551 previous piece for this space; it will have its own mover, and thus
552 will be stored elsewhere. */
553 previous = pmover->piece_previous;
554 if(isexplosion(pmover->piece) && level_previousmoving(plevel, pmover->x, pmover->y) != MOVE_NONE)
555 previous = PIECE_SPACE;
556
557 mover_newundo(plevel, pmover->x, pmover->y,
558 pmover->direction, pmover->piece, previous,
559 MOVER_STORE | (pmover->next == NULL ? 0 : MOVER_FAST));
560
561 if(options_debug & DEBUG_MOVERS)
562 fprintf(stderr, "[%d] Storing undo mover at (%d,%d) is %s was %s (direction=%c) (flags=%d)\n",
563 count ++, pmover->x, pmover->y,
564 piece_name[pmover->piece], piece_name[previous],
565 directiontochar(pmover->direction),
566 (pmover->next == NULL ? 0 : MOVER_FAST));
567
568 pmover = pmover->next;
569 }
570 }
571
level_evolve(struct level * plevel)572 int level_evolve(struct level* plevel)
573 {
574 struct mover* poldmovers;
575 struct mover* pmover;
576 int x, y;
577 int i;
578 int d;
579 int ad;
580 int ed;
581 int ax, ay;
582 int bp, bd;
583 int filled;
584
585 #ifdef XOR_COMPATIBILITY
586 if(plevel->mode == MODE_XOR && options_xor_mode == 1)
587 {
588 return xor_evolve(plevel);
589 }
590 #endif
591 #ifdef ENIGMA_COMPATIBILITY
592 if(plevel->mode == MODE_ENIGMA && options_enigma_mode == 1)
593 {
594 return enigma_evolve(plevel);
595 }
596 #endif
597
598 poldmovers = plevel->mover_first;
599
600 plevel->mover_first = NULL;
601 plevel->mover_last = NULL;
602
603 /* Chroma's engine isn't perfect. Pieces that appear to be in continuous
604 motion are actually momentarily stationary at the start of every cycle.
605 In pathological cases, this can give rise to some counterintuitive
606 situations, where the outcome depends on the order of the movers.
607
608 See levels/regression/chroma-regression.chroma for some examples.
609 */
610
611 pmover = poldmovers;
612 while(pmover != NULL)
613 {
614 level_setmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
615 level_setprevious(plevel, pmover->x, pmover->y, PIECE_SPACE);
616 level_setpreviousmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
617 level_setdetonator(plevel, pmover->x, pmover->y, PIECE_SPACE);
618 level_setdetonatormoving(plevel, pmover->x, pmover->y, MOVE_NONE);
619 pmover = pmover->next;
620 }
621
622 pmover = poldmovers;
623 while(pmover != NULL)
624 {
625
626 /* Remove the mover if something has already moved into its space */
627 if(level_moving(plevel, pmover->x, pmover->y) != MOVE_NONE
628 /* or it isn't what it should be */
629 || level_piece(plevel, pmover->x, pmover->y) != pmover->piece
630 )
631 pmover->piece = PIECE_GONE;
632
633 switch(pmover->piece)
634 {
635 case PIECE_SPACE:
636 case PIECE_EXPLOSION_RED_LEFT:
637 case PIECE_EXPLOSION_RED_HORIZONTAL:
638 case PIECE_EXPLOSION_RED_RIGHT:
639 case PIECE_EXPLOSION_RED_TOP:
640 case PIECE_EXPLOSION_RED_VERTICAL:
641 case PIECE_EXPLOSION_RED_BOTTOM:
642 case PIECE_EXPLOSION_GREEN_LEFT:
643 case PIECE_EXPLOSION_GREEN_HORIZONTAL:
644 case PIECE_EXPLOSION_GREEN_RIGHT:
645 case PIECE_EXPLOSION_GREEN_TOP:
646 case PIECE_EXPLOSION_GREEN_VERTICAL:
647 case PIECE_EXPLOSION_GREEN_BOTTOM:
648 case PIECE_EXPLOSION_BLUE_LEFT:
649 case PIECE_EXPLOSION_BLUE_HORIZONTAL:
650 case PIECE_EXPLOSION_BLUE_RIGHT:
651 case PIECE_EXPLOSION_BLUE_TOP:
652 case PIECE_EXPLOSION_BLUE_VERTICAL:
653 case PIECE_EXPLOSION_BLUE_BOTTOM:
654 i = 0;
655 filled = 0;
656
657 /* Consider the pieces around the space */
658 for(i = 0; i < 4; i ++)
659 {
660 if(filled)
661 continue;
662
663 #ifdef ENIGMA_COMPATIBILITY
664 /* Enigma has a fixed move order */
665 if(plevel->mode == MODE_ENIGMA)
666 d = enigma_move_order[i];
667 else
668 #endif
669 /* Chroma and XOR depend on how the space was emptied */
670 d = (pmover->direction + i) % 4;
671
672 ad = (d + 2) % 4;
673 ax = pmover->x + move_x[ad];
674 ay = pmover->y + move_y[ad];
675
676 /* Can the piece move into the space? */
677 if(canfall(level_piece(plevel, ax, ay), PIECE_SPACE, d)
678 /* and that piece isn't already moving */
679 && level_moving(plevel, ax, ay) == MOVE_NONE
680 )
681 {
682 x = pmover->x + move_x[d];
683 y = pmover->y + move_y[d];
684
685 /* Can the piece from the opposite direction also
686 move into this space? */
687 if(canfall(level_piece(plevel, x, y), PIECE_SPACE, ad)
688 /* and that piece isn't already moving */
689 && level_moving(plevel, x, y) == MOVE_NONE
690 /* If so, can the two explode? */
691 && canexplode(level_piece(plevel, ax, ay), level_piece(plevel, x, y), d, 1, plevel->mode)
692 /* (but not for XOR and Enigma) */
693 && plevel->mode == MODE_CHROMA
694 )
695 {
696 /* If so, detonate them in the middle */
697 if((level_piece(plevel, x, y) & 4) == 4)
698 {
699 /* The first piece is the bomb */
700 bp = level_piece(plevel, x, y);
701 bd = ad;
702 ed = level_piece(plevel, x, y) & 3;
703
704 level_setdetonator(plevel, pmover->x, pmover->y, level_piece(plevel, ax, ay));
705 level_setdetonatormoving(plevel, pmover->x, pmover->y, d);
706 }
707 else
708 {
709 /* The second piece is the bomb */
710 bp = level_piece(plevel, ax, ay);
711 bd = d;
712 ed = level_piece(plevel, ax, ay) & 3;
713
714 level_setdetonator(plevel, pmover->x, pmover->y, level_piece(plevel, x, y));
715 level_setdetonatormoving(plevel, pmover->x, pmover->y, ad);
716 }
717
718 /* and consider anything following them */
719 mover_consider(plevel, x, y, ad);
720 mover_consider(plevel, ax, ay, d);
721
722 /* Move the bomb into the space */
723 level_setpiece(plevel, pmover->x, pmover->y, bp);
724 level_setmoving(plevel, pmover->x, pmover->y, bd);
725
726 /* and explode it */
727 mover_explode(plevel, pmover->x, pmover->y, ed, explosiontype(bp));
728
729 /* Create the central explosion now, to prevent the
730 piece there being processed as a later mover. */
731 level_setpiece(plevel, pmover->x, pmover->y, explosiontype(bp));
732
733 explode_sides(plevel, pmover->x, pmover->y, bp, ed);
734
735 filled = 1;
736 break;
737 }
738
739 /* Otherwise, keep the piece moving */
740 mover_new(plevel, pmover->x, pmover->y, d, level_piece(plevel, ax, ay), 1);
741 /* and see if anything is following in its trail */
742 mover_consider(plevel, ax, ay, d);
743
744 filled = 1;
745 break;
746 }
747 }
748
749 /* If the explosion has not been filled */
750 if(isexplosion(pmover->piece) && filled == 0
751 /* and nothing else is moving into it */
752 && level_moving(plevel, pmover->x, pmover->y) == MOVE_NONE
753 )
754 /* then turn it into a space */
755 mover_new(plevel, pmover->x, pmover->y, pmover->direction, PIECE_SPACE, 0);
756
757 break;
758
759 case PIECE_PLAYER_ONE:
760 case PIECE_PLAYER_TWO:
761 case PIECE_GONE:
762 /* These 'movers' are purely for cosmetic purposes */
763 break;
764
765 #ifdef XOR_COMPATIBILITY
766 case PIECE_TELEPORT:
767 /* These 'movers' are purely for cosmetic purposes */
768 break;
769 #endif
770
771 default:
772 /* A pushed arrow still falls in its natural direction */
773 if(pmover->fast == 0 && pmover->piece >= PIECE_MOVERS_FIRST && pmover->piece <= PIECE_MOVERS_LAST)
774 pmover->direction = pmover->piece % 4;
775
776 /* Consider the space in front of the mover */
777 x = pmover->x + move_x[pmover->direction];
778 y = pmover->y + move_y[pmover->direction];
779
780 /* Can the mover move into the space in front of it? */
781 if(canmove(pmover->piece, level_piece(plevel, x, y), pmover->direction, pmover->fast)
782 /* and that space doesn't already have something
783 moving into it */
784 && (level_moving(plevel, x, y) == MOVE_NONE)
785 )
786 {
787 /* If so, keep it moving */
788 mover_new(plevel, x, y, pmover->direction, pmover->piece, 1);
789 /* and see if anything is following in its trail */
790 mover_consider(plevel, pmover->x, pmover->y, pmover->direction);
791 break;
792 }
793
794 /* Can the mover explode the piece in front of it? */
795 if(canexplode(pmover->piece, level_piece(plevel, x, y), pmover->direction, pmover->fast, plevel->mode)
796 /* and the piece in front isn't moving */
797 && (level_moving(plevel, x, y) == MOVE_NONE
798 /* or it is moving towards us */
799 || (level_moving(plevel, x, y) == ((pmover->direction + 2) % 4)
800 /* (but not for XOR or Enigma) */
801 && plevel->mode==MODE_CHROMA))
802 )
803 {
804 bp = level_piece(plevel, x, y);
805 level_setdetonator(plevel, x, y, pmover->piece);
806 level_setdetonatormoving(plevel, x, y, pmover->direction);
807
808 /* Explosion direction is bomb fall direction */
809 if(bp & 4)
810 ed = bp & 3;
811 else
812 ed = pmover->piece & 3;
813
814 mover_explode(plevel, x, y, ed, explosiontype(bp));
815
816 /* Create the central explosion now, to prevent the piece
817 there being processed as a later mover. */
818 level_setpiece(plevel, x, y, explosiontype(bp));
819
820 mover_consider(plevel, pmover->x, pmover->y, pmover->direction);
821
822 explode_sides(plevel, x, y, bp, ed);
823
824 break;
825 }
826 }
827
828 pmover = pmover->next;
829 }
830
831 /* Create the side explosions at the end, rather than during the previous
832 loop. This allows multiple explosions to occur in parallel. Centre
833 explosions will have already been created earlier on. */
834 pmover = plevel->mover_first;
835 while(pmover != NULL)
836 {
837 if(isnewexplosion(pmover->piece))
838 {
839 if(!isnewexplosion(level_piece(plevel, pmover->x, pmover->y)))
840 {
841 level_setprevious(plevel, pmover->x, pmover->y, level_piece(plevel, pmover->x, pmover->y));
842 level_setpreviousmoving(plevel, pmover->x, pmover->y, level_moving(plevel, pmover->x, pmover->y));
843 }
844
845 /* Use PIECE_EXPLOSION_NEW to allow detection of overlapping
846 explosions further down. */
847 level_setpiece(plevel, pmover->x, pmover->y, pmover->piece);
848 level_setmoving(plevel, pmover->x, pmover->y, pmover->direction);
849
850 pmover->piece += PIECE_EXPLOSION_FIRST - PIECE_EXPLOSION_NEW_FIRST;
851 }
852
853 pmover = pmover->next;
854 }
855
856 pmover = plevel->mover_first;
857 while(pmover != NULL)
858 {
859 if(isexplosion(pmover->piece))
860 {
861 /* Remove any explosions that overlap other explosions */
862 if(isexplosion(level_piece(plevel, pmover->x, pmover->y)))
863 pmover->piece = PIECE_GONE;
864 /* Otherwise, convert new explosions into explosions proper */
865 else
866 level_setpiece(plevel, pmover->x, pmover->y, pmover->piece);
867 }
868 /* Remove any movers that have exploded, or aren't as they should be */
869 if(level_piece(plevel, pmover->x, pmover->y) != pmover->piece)
870 {
871 pmover->piece = PIECE_GONE;
872 }
873 pmover = pmover->next;
874 }
875
876 /* Is player one still alive? */
877 if(level_piece(plevel, plevel->player_x[0], plevel->player_y[0]) != PIECE_PLAYER_ONE)
878 {
879 plevel->flags |= LEVELFLAG_MOVES;
880 plevel->alive[0] = 0;
881 }
882
883 /* Is player two still alive? */
884 if(level_piece(plevel, plevel->player_x[1], plevel->player_y[1]) != PIECE_PLAYER_TWO)
885 {
886 plevel->flags |= LEVELFLAG_MOVES;
887 plevel->alive[1] = 0;
888 }
889
890 /* Free old movers */
891 while(poldmovers != NULL)
892 {
893 pmover = poldmovers;
894 poldmovers = poldmovers->next;
895 free(pmover);
896 }
897
898 return 0;
899 }
900
mover_consider(struct level * plevel,int x,int y,int d)901 void mover_consider(struct level* plevel, int x, int y, int d)
902 {
903 int tx, ty;
904 int ad;
905
906 /* Is there already a mover in this space? If so, don't allow another */
907 if(level_moving(plevel, x, y) != MOVE_NONE)
908 return;
909
910 #ifdef ENIGMA_COMPATIBILITY
911 /* Enigma doesn't consider the direction in which a space was emptied */
912 if(plevel->mode == MODE_ENIGMA)
913 {
914 mover_new(plevel, x, y, d, PIECE_SPACE, 1);
915 return;
916 }
917 #endif
918
919 #ifdef XOR_COMPATIBILITY
920 if(plevel->mode == MODE_XOR)
921 {
922 mover_new(plevel, x, y, d, PIECE_SPACE, 1);
923 return;
924 }
925 #endif
926
927 ad = (d + 2) % 4;
928 tx = x + move_x[ad];
929 ty = y + move_y[ad];
930
931 /* Can a piece follow in the trail of this one? */
932 if(canfall(level_piece(plevel, tx, ty), PIECE_SPACE, d))
933 {
934 /* If it's moving already, just clear this space (1.07) */
935 if(level_moving(plevel, tx, ty) != MOVE_NONE)
936 {
937 mover_new(plevel, x, y, MOVE_NONE, PIECE_SPACE, 0);
938 return;
939 }
940
941 /* Otherwise, set it moving */
942 mover_new(plevel, x, y, d, level_piece(plevel, tx, ty), 1);
943 /* and see if there's anything following in its trail */
944 mover_consider(plevel, tx, ty, d);
945 return;
946 }
947
948 mover_new(plevel, x, y, d, PIECE_SPACE, 1);
949 }
950
explode_sides(struct level * plevel,int x,int y,int p,int d)951 void explode_sides(struct level* plevel, int x, int y, int p, int d)
952 {
953 /* Chroma is subtle. This may be too subtle to have any effect in practice,
954 but the principle elsewhere is that things should be rotationally
955 symmetric, and this carries through here. */
956 if(plevel->mode == MODE_CHROMA)
957 {
958 switch(p % 4)
959 {
960 case 0: /* left */
961 mover_explode(plevel, x, y - 1, d, explosiontype(p) - 1);
962 mover_explode(plevel, x, y + 1, d, explosiontype(p) + 1);
963 break;
964
965 case 1: /* up */
966 mover_explode(plevel, x + 1, y, d, explosiontype(p) + 1);
967 mover_explode(plevel, x - 1, y, d, explosiontype(p) - 1);
968 break;
969
970 case 2: /* right */
971 mover_explode(plevel, x, y + 1, d, explosiontype(p) + 1);
972 mover_explode(plevel, x, y - 1, d, explosiontype(p) - 1);
973 break;
974
975 case 3: /* down */
976 mover_explode(plevel, x - 1, y, d, explosiontype(p) - 1);
977 mover_explode(plevel, x + 1, y, d, explosiontype(p) + 1);
978 break;
979 }
980 }
981 else
982 {
983 switch(p % 2)
984 {
985 case 0: /* left / right */
986 mover_explode(plevel, x, y - 1, d, explosiontype(p) - 1);
987 mover_explode(plevel, x, y + 1, d, explosiontype(p) + 1);
988 break;
989 case 1: /* up /down */
990 mover_explode(plevel, x - 1, y, d, explosiontype(p) - 1);
991 mover_explode(plevel, x + 1, y, d, explosiontype(p) + 1);
992 break;
993 }
994 }
995 }
996
997
canfall(int p,int into,int d)998 int canfall(int p, int into, int d)
999 {
1000 /* Determine whether a piece can start moving */
1001
1002 /* Arrows and bombs */
1003 if(p >= PIECE_MOVERS_FIRST && p<= PIECE_MOVERS_LAST)
1004 {
1005 /* can start falling in their natural direction */
1006 if(d == (p % 4))
1007 {
1008 /* but only into empty space */
1009 if(into == PIECE_SPACE)
1010 return 1;
1011 #ifdef XOR_COMPATIBILITY
1012 /* or into directional dots if appropriate */
1013 if(into == PIECE_DOTS_X && (d == MOVE_LEFT || d == MOVE_RIGHT ))
1014 return 1;
1015 if(into == PIECE_DOTS_Y && (d == MOVE_UP || d == MOVE_DOWN ))
1016 return 1;
1017 #endif
1018 }
1019 }
1020
1021 return 0;
1022 }
1023
1024
canmove(int p,int into,int d,int fast)1025 int canmove(int p, int into, int d, int fast)
1026 {
1027 /* Determine whether a piece can continue moving */
1028
1029 /* Arrows and bombs */
1030 if(p >= PIECE_MOVERS_FIRST && p<= PIECE_MOVERS_LAST)
1031 {
1032 /* can continue moving in their natural direction */
1033 if(d == (p % 4))
1034 {
1035 /* into empty space */
1036 if(into == PIECE_SPACE)
1037 return 1;
1038 /* into dots if they're already moving */
1039 if(into == PIECE_DOTS && fast)
1040 return 1;
1041 #ifdef XOR_COMPATIBILITY
1042 /* into directional dots if appropriate */
1043 if(into == PIECE_DOTS_X && (d == MOVE_LEFT || d == MOVE_RIGHT ))
1044 return 1;
1045 if(into == PIECE_DOTS_Y && (d == MOVE_UP || d == MOVE_DOWN ))
1046 return 1;
1047 #endif
1048 /* through dying explosions */
1049 if(isexplosion(into))
1050 return 1;
1051 /* can kill players if already moving */
1052 if(into == PIECE_PLAYER_ONE && fast)
1053 return 1;
1054 if(into == PIECE_PLAYER_TWO && fast)
1055 return 1;
1056 }
1057 return 0;
1058 }
1059
1060 /* Circles */
1061 if(p == PIECE_CIRCLE)
1062 {
1063 /* are stopped by everything other than empty space */
1064 if(into == PIECE_SPACE)
1065 return 1;
1066 /* and dying explosions */
1067 if(isexplosion(into))
1068 return 1;
1069 return 0;
1070 }
1071
1072 return 0;
1073 }
1074
canbepushed(int p,int into,int d,int mode)1075 int canbepushed(int p, int into, int d, int mode)
1076 {
1077 /* Determine whether a piece can be pushed by the player */
1078
1079 /* Arrows and bombs */
1080 if(p >= PIECE_MOVERS_FIRST && p<= PIECE_MOVERS_LAST)
1081 {
1082 /* can be pushed, but not against their natural direction */
1083 if(d != ((p + 2) % 4))
1084 {
1085 /* into empty space or through dots */
1086 if(into == PIECE_SPACE || into == PIECE_DOTS)
1087 return 1;
1088 #ifdef XOR_COMPATIBILITY
1089 /* through directional dots if appropriate */
1090 if(into == PIECE_DOTS_X && (d == MOVE_LEFT || d == MOVE_RIGHT))
1091 return 1;
1092 if(into == PIECE_DOTS_Y && (d == MOVE_UP || d == MOVE_DOWN))
1093 return 1;
1094 #endif
1095 }
1096 return 0;
1097 }
1098
1099 /* Circles can be pushed in any direction */
1100 if(p == PIECE_CIRCLE
1101 #ifdef ENIGMA_COMPATIBILITY
1102 || p == PIECE_CIRCLE_DOUBLE
1103 #endif
1104 )
1105 {
1106 /* into empty space */
1107 if(into == PIECE_SPACE)
1108 return 1;
1109 #ifdef XOR_COMPATIBILITY
1110 /* XOR won't let circles (dolls) pass through dots */
1111 if(mode == MODE_XOR)
1112 return 0;
1113 #endif
1114 /* pushed through dots */
1115 if(into == PIECE_DOTS)
1116 return 1;
1117 return 0;
1118 }
1119
1120 return 0;
1121 }
1122
canexplode(int p,int i,int d,int fast,int mode)1123 int canexplode(int p, int i, int d, int fast, int mode)
1124 {
1125 /* Only an already moving arrow or bomb can act as a detonator */
1126 if(fast == 0)
1127 return 0;
1128
1129 /* Arrows can detonate bombs */
1130 if(p >= PIECE_ARROW_RED_LEFT && p<= PIECE_ARROW_RED_DOWN &&
1131 i >= PIECE_BOMB_RED_LEFT && i<= PIECE_BOMB_RED_DOWN)
1132 return 1;
1133 if(p >= PIECE_ARROW_GREEN_LEFT && p<= PIECE_ARROW_GREEN_DOWN &&
1134 i >= PIECE_BOMB_GREEN_LEFT && i<= PIECE_BOMB_GREEN_DOWN)
1135 return 1;
1136 if(p >= PIECE_ARROW_BLUE_LEFT && p<= PIECE_ARROW_BLUE_DOWN &&
1137 i >= PIECE_BOMB_BLUE_LEFT && i<= PIECE_BOMB_BLUE_DOWN)
1138 return 1;
1139
1140 #ifdef ENIGMA_COMPATIBILITY
1141 /* Enigma requires a moving arrow to detonate a stationary bomb, and
1142 does not permit bombs to detonate other bombs */
1143 if(mode == MODE_ENIGMA)
1144 return 0;
1145 #endif
1146
1147 /* Bombs can be detonated by arrows pointing towards them */
1148 if(p >= PIECE_BOMB_RED_LEFT && p<= PIECE_BOMB_RED_DOWN &&
1149 i == (PIECE_ARROW_RED_LEFT + ((d + 2) % 4)))
1150 return 1;
1151 if(p >= PIECE_BOMB_GREEN_LEFT && p<= PIECE_BOMB_GREEN_DOWN &&
1152 i == (PIECE_ARROW_GREEN_LEFT + ((d + 2) % 4)))
1153 return 1;
1154 if(p >= PIECE_BOMB_BLUE_LEFT && p<= PIECE_BOMB_BLUE_DOWN &&
1155 i == (PIECE_ARROW_BLUE_LEFT + ((d + 2) % 4)))
1156 return 1;
1157
1158 /* Bombs can detonate other bombs */
1159 if(p >= PIECE_BOMB_RED_LEFT && p<= PIECE_BOMB_RED_DOWN &&
1160 i >= PIECE_BOMB_RED_LEFT && i<= PIECE_BOMB_RED_DOWN)
1161 return 1;
1162 if(p >= PIECE_BOMB_GREEN_LEFT && p<= PIECE_BOMB_GREEN_DOWN &&
1163 i >= PIECE_BOMB_GREEN_LEFT && i<= PIECE_BOMB_GREEN_DOWN)
1164 return 1;
1165 if(p >= PIECE_BOMB_BLUE_LEFT && p<= PIECE_BOMB_BLUE_DOWN &&
1166 i >= PIECE_BOMB_BLUE_LEFT && i<= PIECE_BOMB_BLUE_DOWN)
1167 return 1;
1168
1169 return 0;
1170 }
1171
mover_newundo(struct level * plevel,int x,int y,int d,int piece,int previous,int flags)1172 struct mover* mover_newundo(struct level* plevel, int x, int y, int d, int piece, int previous, int flags)
1173 {
1174 struct mover* pmover;
1175
1176 static int count = 0;
1177
1178 if(plevel->flags & LEVELFLAG_NOUNDO)
1179 return NULL;
1180
1181 pmover = (struct mover*)malloc(sizeof(struct mover));
1182 if(pmover == NULL)
1183 fatal("Out of memory in mover_newundo()");
1184
1185 pmover->x = x;
1186 pmover->y = y;
1187 pmover->direction = d;
1188 pmover->piece = piece;
1189 pmover->piece_previous = previous;
1190 pmover->next = NULL;
1191 pmover->previous = plevel->mover_last;
1192
1193 if(flags & MOVER_FAST)
1194 pmover->fast = 1;
1195 else
1196 pmover->fast = 0;
1197
1198 if(flags & MOVER_UNDO)
1199 {
1200 level_setmoving(plevel, pmover->x, pmover->y, pmover->direction);
1201
1202 if(options_debug & DEBUG_MOVERS)
1203 {
1204 if(plevel->mover_first == NULL)
1205 count = 0;
1206
1207 fprintf(stderr, "[%d] Cosmetic mover at (%d,%d) is %s was %s (direction=%c) (flags=%d)\n",
1208 count ++, pmover->x, pmover->y,
1209 piece_name[pmover->piece], piece_name[pmover->piece_previous],
1210 directiontochar(pmover->direction), pmover->fast);
1211 }
1212
1213 if(plevel->mover_first == NULL)
1214 plevel->mover_first = pmover;
1215
1216 if(plevel->mover_last != NULL)
1217 plevel->mover_last->next = pmover;
1218
1219 plevel->mover_last = pmover;
1220
1221 }
1222
1223 if(flags & MOVER_STORE)
1224 {
1225 pmover->previous = plevel->move_current->mover_last;
1226 pmover->next = NULL;
1227
1228 if(plevel->move_current->mover_first == NULL)
1229 plevel->move_current->mover_first = pmover;
1230 if(plevel->move_current->mover_last != NULL)
1231 plevel->move_current->mover_last->next = pmover;
1232 plevel->move_current->mover_last = pmover;
1233
1234 }
1235
1236 return pmover;
1237 }
1238
1239
level_undo(struct level * plevel)1240 int level_undo(struct level* plevel)
1241 {
1242 struct mover* pmover;
1243 struct mover* ptmp;
1244 struct mover* pmoverfirst;
1245
1246 int d, td;
1247
1248 int count = 0;
1249
1250 /* Can't undo if the level has no undo data (eg, a partial save) */
1251 if(plevel->move_first == NULL || (plevel->move_first->mover_first == NULL && plevel->move_current != plevel->move_first))
1252 return 0;
1253
1254 /* Working backwards, undo any changes made to the map by movers in the
1255 previous step. */
1256 pmoverfirst = NULL;
1257 pmover = plevel->mover_first;
1258 while(pmover != NULL)
1259 {
1260 pmoverfirst = pmover;
1261 pmover = pmover->next;
1262 }
1263 pmover = pmoverfirst;
1264 while(pmover != NULL)
1265 {
1266 /* Not setting SPACEs fixes a pathological case without apparently breaking anything (1.07) */
1267 if(pmover->piece != PIECE_SPACE)
1268 {
1269 level_setpiece(plevel, pmover->x, pmover->y, pmover->piece);
1270 if(options_debug & DEBUG_MOVERS)
1271 fprintf(stderr, "+ level_setpiece(%d, %d, %s)\n", pmover->x, pmover->y, piece_name[pmover->piece]);
1272 }
1273
1274 pmover = pmover->previous;
1275 if(pmover == NULL)
1276 break;
1277 }
1278
1279 /* Is player one still alive? */
1280 if(level_piece(plevel, plevel->player_x[0], plevel->player_y[0]) != PIECE_PLAYER_ONE)
1281 plevel->alive[0] = 0;
1282 /* Is player two still alive? */
1283 if(level_piece(plevel, plevel->player_x[1], plevel->player_y[1]) != PIECE_PLAYER_TWO)
1284 plevel->alive[1] = 0;
1285
1286 /* Tidy up any movers created in the previous step */
1287 pmover = plevel->mover_first;
1288 while(pmover != NULL)
1289 {
1290 level_setmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
1291 level_setprevious(plevel, pmover->x, pmover->y, PIECE_SPACE);
1292 ptmp = pmover;
1293 pmover = pmover->next;
1294 free(ptmp);
1295 }
1296 plevel->mover_first = NULL;
1297 plevel->mover_last = NULL;
1298
1299 /* Can't undo at very start of level */
1300 if(plevel->move_current == NULL)
1301 return 0;
1302
1303 /* If there is no previous step to undo, remove this move entirely */
1304 if(plevel->move_current->mover_last == NULL)
1305 {
1306 plevel->move_current = plevel->move_current->previous;
1307 plevel->flags |= LEVELFLAG_MOVES;
1308
1309 return 0;
1310 }
1311
1312 if(options_debug & DEBUG_MOVERS)
1313 fprintf(stderr, "\n");
1314
1315 /* Start from the last mover for this step. */
1316 pmover = plevel->move_current->mover_last;
1317
1318 pmoverfirst = NULL;
1319
1320 td = MOVE_NONE;
1321
1322 /* Working backwards, remove these pieces from the map */
1323 while(pmover != NULL)
1324 {
1325 pmoverfirst = pmover;
1326
1327 level_setpiece(plevel, pmover->x, pmover->y, PIECE_SPACE);
1328
1329 if(options_debug & DEBUG_MOVERS)
1330 fprintf(stderr, "- level_setpiece(%d, %d, %s)\n", pmover->x, pmover->y, piece_name[PIECE_SPACE]);
1331
1332 /* If the piece is the player, update position and status */
1333 if(pmover->piece_previous == PIECE_PLAYER_ONE || pmover->piece_previous == PIECE_PLAYER_TWO)
1334 {
1335 plevel->player_x[pmover->piece_previous - PIECE_PLAYER_ONE] = pmover->x;
1336 plevel->player_y[pmover->piece_previous - PIECE_PLAYER_ONE] = pmover->y;
1337
1338 #ifdef XOR_COMPATIBILITY
1339 if(plevel->mode == MODE_XOR)
1340 {
1341 /* If a player is being resurrected in this move, and the
1342 other player is alive, undo the automatic swap */
1343 if(plevel->alive[pmover->piece_previous - PIECE_PLAYER_ONE] == 0 && plevel->alive[plevel->player])
1344 {
1345 /* Cosmetic mover to deactivate other player */
1346 mover_newundo(plevel, plevel->player_x[plevel->player], plevel->player_y[plevel->player], MOVE_SWAPPED, PIECE_PLAYER_ONE + plevel->player, PIECE_SPACE, MOVER_UNDO);
1347 plevel->player = pmover->piece_previous - PIECE_PLAYER_ONE;
1348 }
1349
1350 /* The active player is the one which moves first
1351 (last in undo */
1352 plevel->player = pmover->piece_previous - PIECE_PLAYER_ONE;
1353 }
1354 #endif
1355
1356 plevel->alive[pmover->piece_previous - PIECE_PLAYER_ONE] = 1;
1357 }
1358
1359 #ifdef XOR_COMPATIBILITY
1360 /* If the piece is a teleport, store the direction of the original move
1361 into it for later use. */
1362 if(pmover->piece == PIECE_TELEPORT)
1363 td = pmover->direction;
1364 #endif
1365
1366 /* until we reach the first mover for this step. */
1367 pmover = pmover->previous;
1368 if(pmover != NULL && pmover->fast == 0)
1369 break;
1370 }
1371
1372 pmover = pmoverfirst;
1373
1374 /* Now, move forwards through the movers and create cosmetic effects. */
1375 while(pmover != NULL)
1376 {
1377
1378 if(options_debug & DEBUG_MOVERS)
1379 fprintf(stderr, "[%d] Undo mover at (%d,%d) is %s was %s (direction=%c) (flags=%d)\n",
1380 count++, pmover->x, pmover->y,
1381 piece_name[pmover->piece], piece_name[pmover->piece_previous],
1382 directiontochar(pmover->direction), pmover->fast);
1383
1384 d = pmover->direction;
1385
1386 if(d != MOVE_NONE && d != MOVE_SWAP && d != MOVE_SWAPPED)
1387 d = (d + 2) % 4;
1388
1389 if(isexplosion(pmover->piece))
1390 {
1391 /* Explosions don't move. */
1392 d = MOVE_NONE;
1393 /* Show dying explosion when undoing new explosion */
1394 if(options_debug & DEBUG_MOVERS)
1395 fprintf(stderr, "* level_setprevious(%d, %d, %s)\n", pmover->x, pmover->y, piece_name[pmover->piece]);
1396 level_setprevious(plevel, pmover->x, pmover->y, pmover->piece);
1397 }
1398
1399 /* Do we need to patch up the direction this piece is moving in? */
1400 /* Is it the player? */
1401 if((pmover->piece_previous == PIECE_PLAYER_ONE || pmover->piece_previous == PIECE_PLAYER_TWO) && (pmover->piece == PIECE_SPACE || pmover->piece == PIECE_GONE))
1402 {
1403 /* If so, are they moving out of a teleport? Use original direction
1404 of move if so. */
1405 if(td != MOVE_NONE)
1406 d = (td + 2) % 4;
1407 }
1408 /* Otherwise, if the previous piece wasn't a move, it must have been a
1409 static piece being eaten by a mover, and thus shouldn't move. */
1410 else if((pmover->piece_previous < PIECE_MOVERS_FIRST || pmover->piece_previous > PIECE_MOVERS_LAST) && pmover->piece_previous != PIECE_CIRCLE
1411 #ifdef ENIGMA_COMPATIBILITY
1412 && pmover->piece_previous != PIECE_CIRCLE_DOUBLE
1413 #endif
1414 )
1415 d = MOVE_NONE;
1416
1417 /* Plot a cosmetic mover. */
1418 if(level_previous(plevel, pmover->x, pmover->y) != PIECE_SPACE)
1419 d = MOVE_NONE;
1420 /* but not if there are overlapping explosions */
1421 if(!(pmover->piece_previous >= PIECE_EXPLOSION_NEW_FIRST && pmover->piece_previous <= PIECE_EXPLOSION_NEW_LAST))
1422 mover_newundo(plevel, pmover->x, pmover->y, d, pmover->piece_previous, PIECE_SPACE, MOVER_UNDO);
1423
1424 pmover = pmover->next;
1425 }
1426
1427 pmover = pmoverfirst->previous;
1428
1429 /* If there is another step, set it up for the next iteration */
1430 if(pmover != NULL)
1431 {
1432 plevel->move_current->mover_last = pmover;
1433 pmover = pmover->next;
1434 }
1435 else
1436 {
1437 pmover = plevel->move_current->mover_first;
1438 plevel->move_current->mover_first = NULL;
1439 plevel->move_current->mover_last = NULL;
1440
1441 plevel->moves --;
1442 }
1443
1444 /* Remove the movers in the step we've just done */
1445 if(pmover != NULL)
1446 {
1447 while(pmover != NULL)
1448 {
1449 /* Undo any pieces exploded or caught */
1450 if(pmover->piece_previous == PIECE_STAR)
1451 {
1452 if(pmover->piece == PIECE_PLAYER_ONE || pmover->piece == PIECE_PLAYER_TWO)
1453 plevel->stars_caught --;
1454 else
1455 plevel->stars_exploded --;
1456
1457 plevel->flags |= LEVELFLAG_STARS;
1458 }
1459 #ifdef XOR_COMPATIBILITY
1460 if(pmover->piece_previous == PIECE_SWITCH)
1461 {
1462 plevel->switched = 1 - plevel->switched;
1463 plevel->flags |= LEVELFLAG_SWITCH;
1464 }
1465 if(pmover->piece_previous == PIECE_MAP_TOP_LEFT)
1466 {
1467 plevel->mapped ^= MAPPED_TOP_LEFT;
1468 plevel->flags |= LEVELFLAG_MAP;
1469 }
1470 if(pmover->piece_previous == PIECE_MAP_TOP_RIGHT)
1471 {
1472 plevel->mapped ^= MAPPED_TOP_RIGHT;
1473 plevel->flags |= LEVELFLAG_MAP;
1474 }
1475 if(pmover->piece_previous == PIECE_MAP_BOTTOM_LEFT)
1476 {
1477 plevel->mapped ^= MAPPED_BOTTOM_LEFT;
1478 plevel->flags |= LEVELFLAG_MAP;
1479 }
1480 if(pmover->piece_previous == PIECE_MAP_BOTTOM_RIGHT)
1481 {
1482 plevel->mapped ^= MAPPED_BOTTOM_RIGHT;
1483 plevel->flags |= LEVELFLAG_MAP;
1484 }
1485 #endif
1486
1487 ptmp = pmover;
1488 pmover = pmover->next;
1489 free(ptmp);
1490 }
1491 }
1492
1493 if(plevel->move_current->mover_last != NULL)
1494 plevel->move_current->mover_last->next = NULL;
1495
1496 /* If the move was a swap, revert to the previous player */
1497 if(plevel->move_current->direction == MOVE_SWAP)
1498 plevel->player = 1 - plevel->player;
1499
1500 /* Have we just undone failure? */
1501 if((plevel->flags & LEVELFLAG_FAILED) && (plevel->alive[0] != 0 || plevel->alive[1] != 0))
1502 {
1503 plevel->flags &= ~LEVELFLAG_FAILED;
1504 plevel->flags |= LEVELFLAG_MOVES;
1505 }
1506
1507 /* Have we just undone success? */
1508 if(plevel->flags & (LEVELFLAG_SOLVED | LEVELFLAG_EXIT))
1509 {
1510 plevel->flags &= ~LEVELFLAG_SOLVED;
1511 plevel->flags &= ~LEVELFLAG_EXIT;
1512 plevel->flags |= LEVELFLAG_STARS;
1513 }
1514
1515 #ifdef XOR_COMPATIBILITY
1516 if(plevel->mode == MODE_XOR)
1517 xor_focus(plevel);
1518 #endif
1519
1520 /* If there are no more steps in this move, chroma-curses needs advanced
1521 warning that the move counter is going to change. */
1522 if(plevel->move_current->mover_last == NULL)
1523 {
1524 plevel->flags |= LEVELFLAG_MOVES;
1525 }
1526
1527 return 1;
1528 }
1529