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