1 /*
2     enigma.c
3 
4     A reverse engineering of the original Enigma game engine. This maintains
5     the usual list of movers, and a separate stack of spaces to be considered.
6     In each round, we examine this stack to generate fresh movers, then examine
7     those movers to determine whether newly exposed spaces should be added to
8     the stack, and also to generate further movers for the next round.
9 
10     See levels/regression/enigma-regression.chroma for some subtleties this
11     catches that aren't handled correctly by the Chroma game engine. Such
12     situations don't seem to occur in the original Enigma levels, however.
13 
14 
15     Copyright (C) 2010-2019 Amf
16 
17     This program is free software; you can redistribute it and/or modify
18     it under the terms of the GNU General Public License as published by
19     the Free Software Foundation; either version 2 of the License, or
20     (at your option) any later version.
21 
22     This program is distributed in the hope that it will be useful,
23     but WITHOUT ANY WARRANTY; without even the implied warranty of
24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25     GNU General Public License for more details.
26 
27     You should have received a copy of the GNU General Public License
28     along with this program; if not, write to the Free Software
29     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
30 */
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 
35 #include "chroma.h"
36 #include "level.h"
37 #include "util.h"
38 
39 #ifdef ENIGMA_COMPATIBILITY
40 
41 extern int move_x[];
42 extern int move_y[];
43 extern int enigma_move_order[];
44 
enigma_move(struct level * plevel,int move)45 int enigma_move(struct level* plevel, int move)
46 {
47     int px, py;
48     int dx, dy;
49     int p;
50     int into;
51 
52     if(plevel->alive[plevel->player] == 0)
53         return 0;
54     if(move == MOVE_SWAP)
55         return 0;
56 
57     px = plevel->player_x[plevel->player];
58     py = plevel->player_y[plevel->player];
59 
60     dx = px + move_x[move];
61     dy = py + move_y[move];
62 
63     /* Can we make the move? */
64     p = level_piece(plevel, dx, dy);
65     switch(p)
66     {
67         /* Pieces that can be collected */
68         case PIECE_SPACE:
69         case PIECE_DOTS:
70         case PIECE_DOTS_DOUBLE:
71             break;
72         case PIECE_STAR:
73             plevel->stars_caught ++;
74             plevel->flags |= LEVELFLAG_STARS;
75             break;
76         case PIECE_DOOR:
77             if(plevel->stars_caught == plevel->stars_total)
78                 plevel->flags |= LEVELFLAG_EXIT;
79             else
80                 return 0;
81             break;
82         /* Pieces that can be pushed */
83         case PIECE_ARROW_RED_LEFT:
84         case PIECE_ARROW_RED_RIGHT:
85         case PIECE_ARROW_RED_UP:
86         case PIECE_ARROW_RED_DOWN:
87         case PIECE_BOMB_RED_LEFT:
88         case PIECE_BOMB_RED_RIGHT:
89         case PIECE_BOMB_RED_UP:
90         case PIECE_BOMB_RED_DOWN:
91             /* Can't push against gravity */
92             if(((level_piece(plevel, dx, dy) + 2) % 4) == move)
93             return 0;
94                 /* fallthrough */
95         case PIECE_CIRCLE:
96         case PIECE_CIRCLE_DOUBLE:
97             /* Can't push into other pieces */
98             into = level_piece(plevel, dx + move_x[move], dy + move_y[move]);
99             if(into != PIECE_SPACE && into != PIECE_DOTS)
100                 return 0;
101             mover_new(plevel, dx + move_x[move], dy + move_y[move], move, p, 0);
102             break;
103 
104             /* Can't move */
105         default:
106             return 0;
107     }
108 
109     mover_new(plevel, dx, dy, move, PIECE_PLAYER_ONE + plevel->player, 0);
110     mover_new(plevel, px, py, move, PIECE_SPACE, 0);
111     level_setmoving(plevel, px, py, MOVE_NONE);
112     plevel->player_x[plevel->player] = dx;
113     plevel->player_y[plevel->player] = dy;
114     mover_addtostack(plevel, px, py, move);
115 
116     return 1;
117 }
118 
enigma_evolve(struct level * plevel)119 int enigma_evolve(struct level* plevel)
120 {
121     struct mover* pmover;
122     struct mover* ptmp;
123 
124     int into;
125 
126     int d;
127     int i, p, ep;
128 
129     int px, py;
130     int dx, dy;
131 
132     int ok;
133 
134     ok = 0;
135     while(!ok)
136     {
137         /* Examine the stack, and generate movers from it */
138         pmover = plevel->stack_first;
139         while(pmover != NULL)
140         {
141             /* Can anything fall into this space? */
142             for(i = 0; i < 4; i ++)
143             {
144                 d = enigma_move_order[i];
145                 px = pmover->x - move_x[d];
146                 py = pmover->y - move_y[d];
147                 p = level_piece(plevel, px, py);
148                 if(p >= PIECE_ARROW_RED_LEFT && p <= PIECE_BOMB_RED_DOWN && (p % 4 == d))
149                 {
150                     if(level_moving(plevel, px, py) == MOVE_NONE)
151                     {
152                         mover_new(plevel, px, py, d, p, 1);
153                         i = 4;
154                     }
155                 }
156             }
157             ptmp = pmover;
158             pmover = pmover->next;
159             free(ptmp);
160         }
161         plevel->stack_first = NULL;
162         plevel->stack_last = NULL;
163 
164         /* Examine the movers, adding new movers to a separate list */
165         pmover = plevel->mover_first;
166         plevel->mover_first = NULL;
167         plevel->mover_last = NULL;
168         while(pmover != NULL)
169         {
170             level_setmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
171             level_setprevious(plevel, pmover->x, pmover->y, PIECE_SPACE);
172             level_setpreviousmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
173             level_setdetonator(plevel, pmover->x, pmover->y, PIECE_SPACE);
174             level_setdetonatormoving(plevel, pmover->x, pmover->y, MOVE_NONE);
175 
176             p = pmover->piece;
177             if(p == PIECE_EXPLOSION_RED_HORIZONTAL || p == PIECE_EXPLOSION_RED_VERTICAL)
178             {
179                 mover_new(plevel, pmover->x, pmover->y, MOVE_NONE, PIECE_SPACE, 0);
180                 mover_addtostack(plevel, pmover->x, pmover->y, MOVE_NONE);
181                 if(p == PIECE_EXPLOSION_RED_HORIZONTAL)
182                 {
183                     if(pmover->x - 1 > 0)
184                     {
185                         mover_new(plevel, pmover->x - 1, pmover->y, MOVE_NONE, PIECE_SPACE, 0);
186                         mover_addtostack(plevel, pmover->x - 1, pmover->y, MOVE_NONE);
187                     }
188                     if(pmover->x + 1 < plevel->size_x - 1)
189                     {
190                         mover_new(plevel, pmover->x + 1, pmover->y, MOVE_NONE, PIECE_SPACE, 0);
191                         mover_addtostack(plevel, pmover->x + 1, pmover->y, MOVE_NONE);
192                     }
193                 }
194                 else
195                 {
196                     if(pmover->y - 1 > 0)
197                     {
198                         mover_new(plevel, pmover->x, pmover->y - 1, MOVE_NONE, PIECE_SPACE, 0);
199                         mover_addtostack(plevel, pmover->x, pmover->y - 1, MOVE_NONE);
200                     }
201                     if(pmover->y + 1 < plevel->size_y - 1)
202                     {
203                         mover_new(plevel, pmover->x, pmover->y + 1, MOVE_NONE, PIECE_SPACE, 0);
204                         mover_addtostack(plevel, pmover->x, pmover->y + 1, MOVE_NONE);
205                     }
206                 }
207             }
208             if((p >= PIECE_ARROW_RED_LEFT && p <= PIECE_BOMB_RED_DOWN) || p == PIECE_CIRCLE)
209             {
210                 if(p == PIECE_CIRCLE)
211                     d = pmover->direction;
212                 else
213                     d = p % 4;
214                 dx = pmover->x + move_x[d];
215                 dy = pmover->y + move_y[d];
216 
217                 into = level_piece(plevel, dx, dy);
218                 /* Can it detonate something? */
219                 if(p >= PIECE_ARROW_RED_LEFT && p <= PIECE_ARROW_RED_DOWN && into >= PIECE_BOMB_RED_LEFT && into <= PIECE_BOMB_RED_DOWN && pmover->fast && level_moving(plevel, dx, dy) == MOVE_NONE)
220                 {
221                     /* Add the central explosion to the stack */
222                     mover_new(plevel, pmover->x, pmover->y, d, PIECE_SPACE, 0);
223                     level_setprevious(plevel, dx, dy, into);
224                     level_setdetonator(plevel, dx, dy, p);
225                     level_setdetonatormoving(plevel, dx, dy, d);
226                     mover_addtostack(plevel, pmover->x, pmover->y, MOVE_NONE);
227 
228                     /* Generate cosmetic side explosions */
229                     if(into % 2)
230                     {
231                         if(dx - 1 > 0)
232                         {
233                             ep = level_piece(plevel, dx - 1, dy);
234                             if(ep == PIECE_STAR)
235                             {
236                                 plevel->stars_exploded ++;
237                                 plevel->flags |= LEVELFLAG_STARS;
238                             }
239                             level_setmoving(plevel, dx - 1, dy, MOVE_NONE);
240                             mover_new(plevel, dx - 1, dy, level_moving(plevel, dx - 1, dy), PIECE_EXPLOSION_RED_LEFT, 1);
241                             level_setprevious(plevel, dx - 1, dy, ep);
242                         }
243                         if(dx + 1 < plevel->size_x - 1)
244                         {
245                             ep = level_piece(plevel, dx + 1, dy);
246                             if(ep == PIECE_STAR)
247                             {
248                                 plevel->stars_exploded ++;
249                                 plevel->flags |= LEVELFLAG_STARS;
250                             }
251                             level_setmoving(plevel, dx + 1, dy, MOVE_NONE);
252                             mover_new(plevel, dx + 1, dy, level_moving(plevel, dx + 1, dy), PIECE_EXPLOSION_RED_RIGHT, 0);
253                             level_setprevious(plevel, dx + 1, dy, ep);
254                         }
255                         mover_new(plevel, dx, dy, MOVE_NONE, PIECE_EXPLOSION_RED_HORIZONTAL, 0);
256                     }
257                     else
258                     {
259                         if(dy - 1 > 0)
260                         {
261                             ep = level_piece(plevel, dx, dy - 1);
262                             if(ep == PIECE_STAR)
263                             {
264                                 plevel->stars_exploded ++;
265                                 plevel->flags |= LEVELFLAG_STARS;
266                             }
267                             level_setmoving(plevel, dx, dy - 1, MOVE_NONE);
268                             mover_new(plevel, dx, dy - 1, level_moving(plevel, dx, dy - 1), PIECE_EXPLOSION_RED_TOP, 0);
269                             level_setprevious(plevel, dx, dy - 1, ep);
270                         }
271                         if(dy + 1 < plevel->size_y - 1)
272                         {
273                             ep = level_piece(plevel, dx, dy + 1);
274                             if(ep == PIECE_STAR)
275                             {
276                                 plevel->stars_exploded ++;
277                                 plevel->flags |= LEVELFLAG_STARS;
278                             }
279                             level_setmoving(plevel, dx, dy + 1, MOVE_NONE);
280                             mover_new(plevel, dx, dy + 1, level_moving(plevel, dx, dy + 1), PIECE_EXPLOSION_RED_BOTTOM, 0);
281                             level_setprevious(plevel, dx, dy + 1, ep);
282                         }
283                         mover_new(plevel, dx, dy, MOVE_NONE, PIECE_EXPLOSION_RED_VERTICAL, 0);
284                     }
285                 }
286                 /* Can it keep moving? */
287                 else if(into == PIECE_SPACE || ((into == PIECE_DOTS || into == PIECE_PLAYER_ONE) && p != PIECE_CIRCLE && pmover->fast == 1))
288                 {
289                     mover_new(plevel, dx, dy, d, p, 1);
290                     mover_new(plevel, pmover->x, pmover->y, d, PIECE_SPACE, 0);
291                     level_setmoving(plevel, pmover->x, pmover->y, MOVE_NONE);
292                     mover_addtostack(plevel, pmover->x, pmover->y, MOVE_NONE);
293                 }
294             }
295 
296             ptmp = pmover->next;
297             free(pmover);
298             pmover = ptmp;
299         }
300 
301         if(plevel->mover_first != NULL || plevel->stack_first == NULL)
302             ok = 1;
303     }
304 
305     /* Is player one still alive? */
306     if(level_piece(plevel, plevel->player_x[0], plevel->player_y[0]) != PIECE_PLAYER_ONE)
307         plevel->alive[0] = 0;
308 
309     return 0;
310 }
311 
312 #endif
313