1 #include <stdio.h>
2 #include <assert.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <ctype.h>
6 
7 #include "externs.h"
8 #include "globals.h"
9 #include "display.h"
10 
11 extern int abs(int);
12 
13 /* forward declarations */
14 static Boolean RunTo(int, int);
15 static void PushMan(int, int);
16 static void FindTarget(int, int, int);
17 static void MoveMan(int, int);
18 static void DoMove(short);
19 static short TestMove(KeySym);
20 static void MakeMove(KeySym);
21 static void MultiPushPacket(int, int);
22 static Boolean ApplyMoves(int moveseqlen, char *moveseq);
23 
24 /* defining the types of move */
25 #define MOVE            1
26 #define PUSH            2
27 #define SAVE            3
28 #define UNSAVE          4
29 #define STOREMOVE       5
30 #define STOREPUSH       6
31 
32 /* This value merely needs to be greater than the longest possible path length
33  * making it the number of squares in the array seems a good bet.
34  */
35 #define BADMOVE MAXROW*MAXCOL
36 
37 /* some simple checking macros to make sure certain moves are legal when
38  * trying to do mouse based movement.
39  */
40 #define ISPLAYER(x,y) ((map[x][y] == player) || (map[x][y] == playerstore))
41 #define ISCLEAR(x,y) ((map[x][y] == ground) || (map[x][y] == store))
42 #define ISPACKET(x,y) ((map[x][y] == packet) || (map[x][y] == save))
43 
44 /* Some macros useful in MultiPushPacket */
45 #define D_RIGHT    0
46 #define D_UP       1
47 #define D_LEFT     2
48 #define D_DOWN     3
49 
50 #define UNVISITED (BADMOVE+1)
51 
52 #define SETPACKET(x, y) map[x][y] = (map[x][y] == ground) ? packet : save
53 #define CLEARPACKET(x, y) map[x][y] = (map[x][y] == packet) ? ground : store
54 #define CAN_GO(x, y, d) \
55   (ISCLEAR(x+Dx[d], y+Dy[d]) && PossibleToReach(x-Dx[d], y-Dy[d]))
56 #define OPDIR(d) ((d+2) % 4)
57 
58 /* Variables used in MultiPushPacket. */
59 static int pmap[MAXROW+1][MAXCOL+1][4];
60 static char dmap[MAXROW+1][MAXCOL+1][4];
61 
62 static int x1, y1;
63 static int Dx[4] = {0, -1,  0, 1};
64 static int Dy[4] = {1,  0, -1, 0};
65 static int moveKeys[4] = {XK_Right, XK_Up, XK_Left, XK_Down};
66 
67 
68 
69 static XEvent xev;
70 static POS tpos1, tpos2, lastppos, lasttpos1, lasttpos2;
71 static char lppc, ltp1c, ltp2c;
72 static short action, lastaction;
73 
74 static Boolean shift = _false_;
75 static Boolean cntrl = _false_;
76 static Boolean displaying = _true_;
77 static KeySym oldmove;
78 static int findmap[MAXROW+1][MAXCOL+1];
79 
80 #define MAXDELTAS 4
81 
82 /** For stack saves. **/
83 struct map_delta {
84     int x,y;
85     char oldchar, newchar;
86 };
87 struct move_r {
88     short px, py;
89     short moves, pushes, saved, ndeltas;
90     struct map_delta deltas[MAXDELTAS];
91 };
92 static struct move_r move_stack[STACKDEPTH];
93 static int move_stack_sp; /* points to last saved move. If no saved move, -1 */
94 static Map prev_map;
95 static void RecordChange(void);
96 static void UndoChange(void);
97 static void InitMoveStack(void);
98 
99 static int tempsave;
100 /* The move index at which a temporary save request was made */
101 
Play(void)102 extern short Play(void)
103 {
104   short ret;
105   int bufs = 1;
106   char buf[1];
107   KeySym sym;
108   XComposeStatus compose;
109 
110   displaying = _true_;
111   ClearScreen();
112   ShowScreen();
113   InitMoveStack();
114   tempsave = moves;
115   ret = 0;
116   while (ret == 0) {
117     XNextEvent(dpy, &xev);
118     switch(xev.type) {
119       case Expose:
120 	/* Redisplaying is pretty cheap, so I don't care too much about it */
121 	RedisplayScreen();
122 	break;
123       case ButtonPress:
124 	switch(xev.xbutton.button) {
125 	  case Button1:
126 	    /* move the man to where the pointer is. */
127 	    MoveMan(xev.xbutton.x, xev.xbutton.y);
128 	    RecordChange();
129 	    break;
130 	  case Button2:
131 	    /* Push a ball */
132 	    PushMan(xev.xbutton.x, xev.xbutton.y);
133 	    RecordChange();
134 	    break;
135 	  case Button3:
136 	    /* undo last move */
137 	    UndoChange();
138 	    ShowScreen();
139 	    break;
140 	  default:
141 	    /* I'm sorry, you win the irritating beep for your efforts. */
142 	    HelpMessage();
143 	    break;
144 	}
145 	RedisplayScreen();
146 	break;
147       case KeyPress:
148 	buf[0] = '\0';
149 	(void) XLookupString(&xev.xkey, buf, bufs, &sym, &compose);
150 	cntrl = (xev.xkey.state & ControlMask) ? _true_ : _false_;
151 	shift = (xev.xkey.state & ShiftMask) ? _true_ : _false_;
152 	switch(sym) {
153 	  case XK_q:
154 	    /* q is for quit */
155 	    if(!cntrl)
156 	      ret = E_ABORTLEVEL;
157 	    break;
158 	  case XK_S:
159 	    if (shift || cntrl) {
160 		ret = DisplayScores(0);
161 		RedisplayScreen();
162 	    }
163 	    break;
164 	  case XK_s:
165 	    /* save */
166 	    if(!cntrl && !shift) {
167 	      ret = SaveGame();
168 	      if(ret == 0)
169 		ret = E_SAVED;
170 	    }
171 	    break;
172 	  case XK_question:
173 	    /* help */
174 	    if(!cntrl) {
175 	      ShowHelp();
176 	      RedisplayScreen();
177 	    }
178 	    break;
179 	  case XK_r:
180 	    /* ^R refreshes the screen */
181 	    if(cntrl)
182 	      RedisplayScreen();
183 	    break;
184 	  case XK_U:
185 	    /* Do a full screen reset */
186 	    if(!cntrl) {
187 	      tempsave = moves = pushes = 0;
188 	      ret = ReadScreen();
189 	      InitMoveStack();
190 	      if(ret == 0) {
191 		ShowScreen();
192 	      }
193 	    }
194 	    break;
195 	  case XK_u:
196 	    if(!cntrl) {
197 		UndoChange();
198 		ShowScreen();
199 	    } else {
200 		Bool ok;
201 		moves = pushes = 0;
202 		ret = ReadScreen();
203 		InitMoveStack();
204 		ok = ApplyMoves(tempsave, move_history);
205 		RecordChange();
206 		assert(ok);
207 		ShowScreen();
208 		assert(tempsave == moves);
209 	    }
210 	    break;
211 	  case XK_c:
212 	    if (!cntrl) {
213 		if (moves < MOVE_HISTORY_SIZE) tempsave = moves;
214 		else tempsave = MOVE_HISTORY_SIZE - 1;
215 	    }
216 	    break;
217 	  case XK_k:
218 	  case XK_K:
219 	  case XK_Up:
220 	  case XK_j:
221 	  case XK_J:
222 	  case XK_Down:
223 	  case XK_l:
224 	  case XK_L:
225 	  case XK_Right:
226 	  case XK_h:
227 	  case XK_H:
228 	  case XK_Left:
229 	    /* Ordinary move keys */
230 	    MakeMove(sym);
231 	    RecordChange();
232 	    break;
233 	  default:
234 	    /* I ONLY want to beep if a key was pressed.  Contrary to what
235 	     * X11 believes, SHIFT and CONTROL are NOT keys
236 	     */
237             if(buf[0]) {
238 	      HelpMessage();
239 	    }
240 	    break;
241 	}
242 	break;
243       case ClientMessage:
244 	{
245 	    XClientMessageEvent *cm = (XClientMessageEvent *)&xev;
246 	    if (cm->message_type == wm_protocols &&
247 		cm->data.l[0] == wm_delete_window)
248 		  ret = E_ENDGAME;
249 	}
250 	break;
251       default:
252 	break;
253     }
254     /* if we solved a level, set it up so we get some score! */
255     if((ret == 0) && (packets == savepack)) {
256       scorelevel = level;
257       scoremoves = moves;
258       scorepushes = pushes;
259       break;
260     }
261   }
262   return ret;
263 }
264 
Verify(int moveseqlen,char * moveseq)265 extern Boolean Verify(int moveseqlen, char *moveseq)
266 {
267     InitMoveStack();
268     tempsave = moves = pushes = 0;
269     if (ApplyMoves(moveseqlen, moveseq) && savepack == packets) {
270 	scorelevel = level;
271 	scoremoves = moves;
272 	scorepushes = pushes;
273 	return _true_;
274     } else {
275 	return _false_;
276     }
277 }
278 
279 /* Perform a user move based on the key in "sym". */
MakeMove(KeySym sym)280 static void MakeMove(KeySym sym)
281 {
282   do {
283     action = TestMove(sym);
284     if(action != 0) {
285       lastaction = action;
286       lastppos.x = ppos.x;
287       lastppos.y = ppos.y;
288       lppc = map[ppos.x][ppos.y];
289       lasttpos1.x = tpos1.x;
290       lasttpos1.y = tpos1.y;
291       ltp1c = map[tpos1.x][tpos1.y];
292       lasttpos2.x = tpos2.x;
293       lasttpos2.y = tpos2.y;
294       ltp2c = map[tpos2.x][tpos2.y];
295       DoMove(lastaction);
296       /* store the current keysym so we can do the repeat. */
297       oldmove = sym;
298     }
299   } while ((action != 0) && (packets != savepack) && (shift || cntrl));
300 }
301 
302 /* make sure a move is valid and if it is, return type of move */
TestMove(KeySym action)303 static short TestMove(KeySym action)
304 {
305   short ret;
306   char tc;
307   Boolean stop_at_object;
308 
309   stop_at_object = cntrl;
310 
311   if((action == XK_Up) || (action == XK_k) || (action == XK_K)) {
312     tpos1.x = ppos.x-1;
313     tpos2.x = ppos.x-2;
314     tpos1.y = tpos2.y = ppos.y;
315     if (moves < MOVE_HISTORY_SIZE) move_history[moves] = 'k';
316   }
317   if((action == XK_Down) || (action == XK_j) || (action == XK_J)) {
318     tpos1.x = ppos.x+1;
319     tpos2.x = ppos.x+2;
320     tpos1.y = tpos2.y = ppos.y;
321     if (moves < MOVE_HISTORY_SIZE) move_history[moves] = 'j';
322   }
323   if((action == XK_Left) || (action == XK_h) || (action == XK_H)) {
324     tpos1.y = ppos.y-1;
325     tpos2.y = ppos.y-2;
326     tpos1.x = tpos2.x = ppos.x;
327     if (moves < MOVE_HISTORY_SIZE) move_history[moves] = 'h';
328   }
329   if((action == XK_Right) || (action == XK_l) || (action == XK_L)) {
330     tpos1.y = ppos.y+1;
331     tpos2.y = ppos.y+2;
332     tpos1.x = tpos2.x = ppos.x;
333     if (moves < MOVE_HISTORY_SIZE) move_history[moves] = 'l';
334   }
335   tc = map[tpos1.x][tpos1.y];
336   if((tc == packet) || (tc == save)) {
337     if(!stop_at_object) {
338       if(map[tpos2.x][tpos2.y] == ground)
339 	ret = (tc == save) ? UNSAVE : PUSH;
340       else if(map[tpos2.x][tpos2.y] == store)
341 	ret = (tc == save) ? STOREPUSH : SAVE;
342       else
343 	ret = 0;
344     } else
345       ret = 0;
346   } else if(tc == ground)
347     ret = MOVE;
348   else if(tc == store)
349     ret = STOREMOVE;
350   else
351     ret = 0;
352   return ret;
353 }
354 
355 /* actually update the internal map with the results of the move */
DoMove(short moveaction)356 static void DoMove(short moveaction)
357 {
358   map[ppos.x][ppos.y] = (map[ppos.x][ppos.y] == player) ? ground : store;
359   switch( moveaction) {
360     case MOVE:
361       map[tpos1.x][tpos1.y] = player;
362       break;
363     case STOREMOVE:
364       map[tpos1.x][tpos1.y] = playerstore;
365       break;
366     case PUSH:
367       map[tpos2.x][tpos2.y] = map[tpos1.x][tpos1.y];
368       map[tpos1.x][tpos1.y] = player;
369       pushes++;
370       break;
371     case UNSAVE:
372       map[tpos2.x][tpos2.y] = packet;
373       map[tpos1.x][tpos1.y] = playerstore;
374       pushes++;
375       savepack--;
376       break;
377     case SAVE:
378       map[tpos2.x][tpos2.y] = save;
379       map[tpos1.x][tpos1.y] = player;
380       savepack++;
381       pushes++;
382       break;
383     case STOREPUSH:
384       map[tpos2.x][tpos2.y] = save;
385       map[tpos1.x][tpos1.y] = playerstore;
386       pushes++;
387       break;
388   }
389   moves++;
390   if (displaying) {
391       DisplayMoves();
392       DisplayPushes();
393       DisplaySave();
394       MapChar(map[ppos.x][ppos.y], ppos.x, ppos.y, 1);
395       MapChar(map[tpos1.x][tpos1.y], tpos1.x, tpos1.y, 1);
396       MapChar(map[tpos2.x][tpos2.y], tpos2.x, tpos2.y, 1);
397       SyncScreen();
398 #ifdef HAS_USLEEP
399       usleep(SLEEPLEN * 1000);
400 #endif
401   }
402   ppos.x = tpos1.x;
403   ppos.y = tpos1.y;
404 }
405 
406 /* find the shortest path to the target via a fill search algorithm */
FindTarget(int px,int py,int pathlen)407 static void FindTarget(int px, int py, int pathlen)
408 {
409   if(!(ISCLEAR(px, py) || ISPLAYER(px, py)))
410     return;
411   if(findmap[px][py] <= pathlen)
412     return;
413 
414   findmap[px][py] = pathlen++;
415 
416   if((px == ppos.x) && (py == ppos.y))
417     return;
418 
419   FindTarget(px - 1, py, pathlen);
420   FindTarget(px + 1, py, pathlen);
421   FindTarget(px, py - 1, pathlen);
422   FindTarget(px, py + 1, pathlen);
423 }
424 
425 /* Do all the fun movement stuff with the mouse */
MoveMan(int mx,int my)426 static void MoveMan(int mx, int my)
427 {
428   int x, y;
429 
430   shift = cntrl = _false_;
431 
432   /* reverse the screen coords to get the internal coords (yes, I know this
433    * should be fixed) RSN */
434   y = wX(mx);
435   x = wY(my);
436 
437   /* make sure we are within the bounds of the array */
438   if((x < 0) || (x > MAXROW) || (y < 0) || (y > MAXCOL)) {
439     HelpMessage();
440     return;
441   }
442 
443   /* If the click was on a packet then try to 'drag' the packet */
444   if(ISPACKET(x, y)) {
445     MultiPushPacket(x, y);
446     return;
447   }
448 
449   /* if we clicked on the player or a wall (or an object but that was already
450    * handled) the we don't want to move.
451    */
452   if(!ISCLEAR(x, y)) {
453     HelpMessage();
454     return;
455   }
456   if (!RunTo(x, y)) HelpMessage();
457 }
458 
459 /* Return whether (x,y) is on the board */
ValidPosn(int x,int y)460 static Boolean ValidPosn(int x, int y)
461 {
462     return (x >= 0) && (x <= MAXROW) && (y >= 0) && (y <= MAXCOL);
463 }
464 
465 /*
466    Find the object at a position orthogonal to (x, y) that is
467    closest to (ppos.x, ppos.y), is separated from (x,y) only
468    by empty spaces, and has the player or an empty space on the far
469    side of (x,y), and is not directly opposite the destination space
470    from the player; place its coordinates in (*ox, *oy) and return _true_.
471    If no such object exists, return _false_.
472 */
FindOrthogonalObject(int x,int y,int * ox,int * oy)473 static Boolean FindOrthogonalObject(int x, int y, int *ox, int *oy)
474 {
475     int dir;
476     int bestdist = BADMOVE;
477     Boolean foundOne = _false_;
478     for (dir = 0; dir < 4; dir++) {
479 	int dx, dy, x1, y1, dist;
480 	switch(dir) {
481 	    default: dx = 1; dy = 0; break; /* use "default" to please gcc */
482 	    case 1: dx = -1; dy = 0; break;
483 	    case 2: dx = 0; dy = 1; break;
484 	    case 3: dx = 0; dy = -1; break;
485 	}
486 	if ((ppos.x == x && ((ppos.y - y)*dy) < 0)
487 	    || (ppos.y == y && ((ppos.x - x)*dx) < 0)) continue;
488 	/* Eliminate case where player would push in the opposite
489 	   direction. */
490 	x1 = x; y1 = y;
491         while (ValidPosn(x1, y1)) {
492 	    x1 += dx; y1 += dy;
493 	    dist = abs(ppos.x - x1) + abs(ppos.y - y1);
494 	    if (dist <= bestdist && ISPACKET(x1, y1) &&
495 		(ISPLAYER(x1 + dx, y1 + dy) || ISCLEAR(x1 + dx, y1 + dy))) {
496 		if (dist < bestdist) {
497 		    bestdist = dist;
498 		    *ox = x1;
499 		    *oy = y1;
500 		    foundOne = _true_;
501 		    break;
502 		} else {
503 		    foundOne = _false_; /* found one that was just as good */
504 		}
505 	    }
506 	    if (!ISCLEAR(x1, y1)) break;
507 	}
508     }
509     return foundOne ? _true_ : _false_ ;
510 }
511 
512 
513 /* Kind of a self explanatory name, ehh? */
PossibleToReach(int x,int y)514 static Boolean PossibleToReach(int x, int y)
515 {
516   int i,j;
517 
518   /* Fill the trace map */
519   for(i = 0; i <= MAXROW; i++)
520     for (j = 0; j <= MAXCOL; j++)
521       findmap[i][j] = BADMOVE;
522 
523   /* flood fill search to find a shortest path to the push point. */
524   FindTarget(x, y, 0);
525   return (findmap[ppos.x][ppos.y] != BADMOVE);
526 }
527 
528 /* Try to find the goal from (x, y), coming from 'direction', */
529 /* having walked 'dist' units.                                */
PushFromDir(int x,int y,int direction,int dist)530 static int PushFromDir(int x, int y, int direction, int dist)
531 {
532   int r, res, d, fx = ppos.x, fy = ppos.y;
533 
534   /* Have we been here before? */
535   if (pmap[x][y][direction] != UNVISITED) {
536     if (pmap[x][y][direction] == BADMOVE)
537       return BADMOVE;
538     else
539       return dist+pmap[x][y][direction]-1;
540   }
541 
542   /* Have we found the target? */
543   if (x==x1 && y==y1) {
544     pmap[x][y][direction] = 1;
545     return dist;
546   }
547 
548   /* Try going in all four directions. Choose the shortest path, if any */
549   res = BADMOVE;
550   pmap[x][y][direction] = BADMOVE;
551   for (d = 0; d < 4; d++) {
552     ppos.x = fx; ppos.y = fy;
553     if (CAN_GO(x, y, d)) {
554       CLEARPACKET(x, y);
555       SETPACKET(x+Dx[d], y+Dy[d]);
556       ppos.x = x;
557       ppos.y = y;
558       r = PushFromDir(x+Dx[d], y+Dy[d], OPDIR(d), dist+1);
559       if (r < res) {
560 	dmap[x][y][direction] = d;
561 	res = r;
562       }
563       SETPACKET(x, y);
564       CLEARPACKET(x+Dx[d], y+Dy[d]);
565     }
566   }
567   pmap[x][y][direction] = (res == BADMOVE) ? BADMOVE : res-dist+1;
568   ppos.x = fx;
569   ppos.y = fy;
570   return res;
571 }
572 
573 /*
574    The player has pressed button two on a packet.
575    Wait for the release of the button and then try to move the
576    packet from the button press coord to the button release coord.
577    Give help message (i.e. beep...) if it is not possible.
578    Code by Jan Sparud.
579 */
MultiPushPacket(int x0,int y0)580 static void MultiPushPacket(int x0, int y0)
581 {
582   int i, j, k, d = 0, r, result;
583   char manChar;
584 
585   /* Wait for button release */
586   XNextEvent(dpy, &xev);
587   while (!(xev.type == ButtonRelease &&
588       (xev.xbutton.button == Button2 ||
589        xev.xbutton.button == Button1)))
590     XNextEvent(dpy, &xev);
591 
592   y1 = wX(xev.xbutton.x);
593   x1 = wY(xev.xbutton.y);
594 
595   if (x0 == x1 && y0 == y1) {
596 	/* Player just clicked on a stone. If man is next to stone,
597 	   just push it once.
598         */
599     if(ISPLAYER(x0 - 1, y0))
600       MakeMove(XK_Down);
601     else if(ISPLAYER(x0 + 1, y0))
602       MakeMove(XK_Up);
603     else if(ISPLAYER(x0, y0 - 1))
604       MakeMove(XK_Right);
605     else if(ISPLAYER(x0, y0 + 1))
606       MakeMove(XK_Left);
607     else {
608       /* we weren't right next to the object */
609       HelpMessage();
610       return;
611     }
612     return;
613   }
614 
615   if (!ValidPosn(x1, y1) || (!ISCLEAR(x1, y1) && !ISPLAYER(x1, y1))) {
616     HelpMessage();
617     return;
618   }
619 
620   /* Remove (temporarily) the player from the board */
621   manChar = map[ppos.x][ppos.y];
622   map[ppos.x][ppos.y] = map[ppos.x][ppos.y] == player ? ground : store;
623 
624   /* Prepare the distance map */
625   for (i=0; i<MAXROW; i++)
626     for (j=0; j<MAXCOL; j++)
627       if (ISCLEAR(i, j) || (i==x0 && j==y0))
628 	for (k=0; k<4; k++)
629 	  pmap[i][j][k] = UNVISITED;
630       else
631 	for (k=0; k<4; k++)
632 	  pmap[i][j][k] = BADMOVE;
633 
634   /* Try to go from the start position in all four directions */
635   result = BADMOVE;
636   for (k = 0; k < 4; k++)
637     if (CAN_GO(x0, y0, k)) {
638       r = PushFromDir(x0, y0, OPDIR(k), 1);
639       if (r < result) {
640 	d = OPDIR(k);
641 	result = r;
642       }
643     }
644 
645   /* Put the player on the board again */
646   map[ppos.x][ppos.y] = manChar;
647 
648   /* If we found a way (i.e. result < BADMOVE) then follow the route */
649   if (result < BADMOVE) {
650     for (i = x0, j = y0; !(i==x1 && j==y1);) {
651       d = OPDIR(dmap[i][j][d]);
652       RunTo(i+Dx[d], j+Dy[d]);
653       i -= Dx[d];
654       j -= Dy[d];
655       MakeMove(moveKeys[OPDIR(d)]);
656     }
657   } else {
658     /* Otherwise, beep */
659     HelpMessage();
660     return;
661   }
662 }
663 
664 /* Push a nearby stone to the position indicated by (mx, my). */
PushMan(int mx,int my)665 static void PushMan(int mx, int my)
666 {
667   int i, x, y, ox, oy, dist;
668 
669   shift = cntrl = _false_;
670 
671   /* reverse the screen coords to get the internal coords (yes, I know this
672    * should be fixed) RSN */
673   y = wX(mx);
674   x = wY(my);
675 
676   /* make sure we are within the bounds of the array */
677   if(!ValidPosn(x,y)) {
678     HelpMessage();
679     return;
680   }
681 
682   /* If the click was on a packet then try to 'drag' the packet */
683   if(ISPACKET(x, y)) {
684     MultiPushPacket(x, y);
685     return;
686   }
687 
688   /* if we clicked on the player or a wall (or an object but that was already
689    * handled) the we don't want to move.
690    */
691   if(!ISCLEAR(x, y)) {
692     HelpMessage();
693     return;
694   }
695 
696   if (!FindOrthogonalObject(x, y, &ox, &oy)) {
697     HelpMessage();
698     return;
699   }
700 
701   assert(x == ox || y == oy);
702   dist = abs(ox - x) + abs(oy - y);
703 
704   if (x > ox) ox--;
705   if (x < ox) ox++;
706   if (y > oy) oy--;
707   if (y < oy) oy++;
708 
709   /* (ox,oy) now denotes the place we need to run to to be able to push */
710 
711   if (ox != ppos.x || oy != ppos.y) {
712       if (!ISCLEAR(ox, oy)) {
713 	HelpMessage();
714 	return;
715       }
716       if (!RunTo(ox, oy)) {
717 	HelpMessage();
718 	return;
719       }
720   }
721   assert(ppos.x == ox && ppos.y == oy);
722 
723   for (i = 0; i < dist; i++) {
724     if (ppos.x < x) MakeMove(XK_Down);
725     if (ppos.x > x) MakeMove(XK_Up);
726     if (ppos.y < y) MakeMove(XK_Right);
727     if (ppos.y > y) MakeMove(XK_Left);
728   }
729 }
730 
731 /* Move the player to the position (x,y), if possible. Return _true_
732    iff successful. The position (x,y) must be clear.
733 */
RunTo(int x,int y)734 static Boolean RunTo(int x, int y)
735 {
736   int i,j,cx,cy;
737   /* Fill the trace map */
738   for(i = 0; i <= MAXROW; i++)
739     for (j = 0; j <= MAXCOL; j++)
740       findmap[i][j] = BADMOVE;
741   /* flood fill search to find a shortest path to the push point. */
742   FindTarget(x, y, 0);
743 
744   /* if we didn't make it back to the players position, there is no valid path
745    * to that place.
746    */
747   if(findmap[ppos.x][ppos.y] == BADMOVE) {
748     return _false_;
749   } else {
750     /* we made it back, so let's walk the path we just built up */
751     cx = ppos.x;
752     cy = ppos.y;
753     while(findmap[cx][cy]) {
754       if(findmap[cx - 1][cy] == (findmap[cx][cy] - 1)) {
755 	MakeMove(XK_Up);
756 	cx--;
757       } else if(findmap[cx + 1][cy] == (findmap[cx][cy] - 1)) {
758 	MakeMove(XK_Down);
759 	cx++;
760       } else if(findmap[cx][cy - 1] == (findmap[cx][cy] - 1)) {
761 	MakeMove(XK_Left);
762 	cy--;
763       } else if(findmap[cx][cy + 1] == (findmap[cx][cy] - 1)) {
764 	MakeMove(XK_Right);
765 	cy++;
766       } else {
767 	/* if we get here, something is SERIOUSLY wrong, so we should abort */
768 	abort();
769       }
770     }
771   }
772   return _true_;
773 }
774 
775 
InitMoveStack()776 static void InitMoveStack()
777 {
778     move_stack_sp = -1;
779     move_stack[0].moves = moves;
780     move_stack[0].pushes = pushes;
781     move_stack[0].saved = savepack;
782     memcpy(prev_map, map, sizeof(map));
783 }
784 
785 /* Add a record to the move stack that records the changes since the
786    last map state (which is stored in "prev_map"). Update "prev_map"
787    to contain the current map so the next call to "RecordChange()"
788    will perform correctly.
789 
790    If the stack runs out of space, dump the oldest half of the
791    saved moves and continue. Undoing past that point will jump
792    back to the beginning of the level. If the user is using the
793    mouse or any skill, should never happen.
794 */
RecordChange()795 static void RecordChange()
796 {
797     struct move_r *r = &move_stack[++move_stack_sp];
798     int x,y, ndeltas = 0;
799     assert(move_stack_sp < STACKDEPTH);
800     if (move_stack_sp == STACKDEPTH - 1) {
801 	int shift = STACKDEPTH/2;
802 	memcpy(&move_stack[0], &move_stack[shift],
803 	      sizeof(struct move_r) * (STACKDEPTH - shift));
804 	move_stack_sp -= shift;
805 	r -= shift;
806     }
807     r[1].moves = moves;
808     r[1].pushes = pushes;
809     r[1].saved = savepack;
810     r[1].px = ppos.x;
811     r[1].py = ppos.y;
812     for (x = 0; x <= MAXROW; x++) {
813 	for (y = 0; y <= MAXROW; y++) {
814 	    if (map[x][y] != prev_map[x][y]) {
815 		assert(ndeltas < MAXDELTAS);
816 		r->deltas[ndeltas].x = x;
817 		r->deltas[ndeltas].y = y;
818 		r->deltas[ndeltas].newchar = map[x][y];
819 		r->deltas[ndeltas].oldchar = prev_map[x][y];
820 		ndeltas++;
821 #if 0
822 		printf("Change (%d,%d) %c->%c\n", x, y, prev_map[x][y],
823 		       map[x][y]);
824 #endif
825 	    }
826 	}
827     }
828     r->ndeltas = ndeltas;
829     if (ndeltas == 0) {
830 	move_stack_sp--; /* Why push an identical entry? */
831     }
832     memcpy(prev_map, map, sizeof(map));
833 }
834 
UndoChange()835 static void UndoChange()
836 {
837     if (move_stack_sp <= 0) {
838 	int ret;
839 	moves = pushes = 0;
840 	ret = ReadScreen();
841 	InitMoveStack();
842 	if (ret) {
843 	    fprintf(stderr, "Can't read screen file\n");
844 	    exit(-1);
845 	}
846     } else {
847 	struct move_r *r = &move_stack[move_stack_sp];
848 	int i;
849 	moves = r->moves;
850 	pushes = r->pushes;
851 	savepack = r->saved;
852 	ppos.x = r->px;
853 	ppos.y = r->py;
854 	for (i = 0; i<r->ndeltas; i++) {
855 #if 0
856 	    printf("Applying reverse change: (%d,%d) %c->%c\n",
857 		r->deltas[i].x, r->deltas[i].y,
858 		   map[r->deltas[i].x][r->deltas[i].y], r->deltas[i].oldchar);
859 #endif
860 	    map[r->deltas[i].x][r->deltas[i].y] = r->deltas[i].oldchar;
861 	}
862 	move_stack_sp--;
863 	memcpy(prev_map, map, sizeof(map));
864     }
865 }
866 
867 char move_history[MOVE_HISTORY_SIZE];
868 
869 /* ApplyMoves:
870 
871     Receive a move sequence, and apply it to the current level as if
872     the player had made the moves, but without doing any screen updates.
873     Return _true_ if the move sequence is well-formed; _false_ if not.
874 
875     "moveseqlen" must be the length in characters of "moveseq".
876 
877     A well-formed move string "moveseq" is a sequence of the characters
878     "h,j,k,l" and "1-9".
879 
880 	[hjkl]: move the man by one in the appropriate direction
881 	[HJKL]: move the man by two in the appropriate direction
882 	[1-9]: provide a count of how many times the next move
883 		should be executed, divided by two. Thus, "1" means
884 		repeat twice, "9" means repeat 18 times.  The next
885 		character must be one of [hjklHJKL].
886 */
SingleMove(char c)887 static Boolean SingleMove(char c)
888 {
889     switch(c) {
890 	case 'h': MakeMove(XK_h); break;
891 	case 'H': MakeMove(XK_h); MakeMove(XK_h); break;
892 	case 'j': MakeMove(XK_j); break;
893 	case 'J': MakeMove(XK_j); MakeMove(XK_j); break;
894 	case 'k': MakeMove(XK_k); break;
895 	case 'K': MakeMove(XK_k); MakeMove(XK_k); break;
896 	case 'l': MakeMove(XK_l); break;
897 	case 'L': MakeMove(XK_l); MakeMove(XK_l); break;
898 	default: return _false_;
899     }
900     return _true_;
901 }
902 
ApplyMoves(int moveseqlen,char * moveseq)903 static Boolean ApplyMoves(int moveseqlen, char *moveseq)
904 {
905     int i = 0;
906     char lastc = 0;
907     Bool olddisp = displaying;
908 
909     displaying = _false_;
910     shift = _false_;
911     cntrl = _false_;
912 
913     while (i < moveseqlen) {
914 	char c = moveseq[i++];
915 	if (lastc && c != lastc) RecordChange();
916 	lastc = c;
917 	switch (c) {
918 	    case 'h': case 'j': case 'k': case 'l':
919 	    case 'H': case 'J': case 'K': case 'L':
920 		SingleMove(c);
921 		break;
922 	    case '1': case '2': case '3':
923 	    case '4': case '5': case '6':
924 	    case '7': case '8': case '9':
925 		{
926 		    int reps = c - '0';
927 		    if (i == moveseqlen) {
928 			displaying = olddisp;
929 			return _false_;
930 		    }
931 		    c = moveseq[i++];
932 		    lastc = tolower(c);
933 		    while (reps--) {
934 			if (!SingleMove(c)) {
935 			    displaying = olddisp;
936 			    return _false_;
937 			}
938 		    }
939 		}
940 		break;
941 	}
942     }
943     RecordChange();
944     displaying = olddisp;
945     return _true_;
946 }
947 
948