home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / games / volume18 / xsokoban4 / part01 / play.c < prev    next >
C/C++ Source or Header  |  1994-02-01  |  18KB  |  746 lines

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <string.h>
  4.  
  5. #include "externs.h"
  6. #include "globals.h"
  7.  
  8. extern int abs(int);
  9.  
  10. /* defining the types of move */
  11. #define MOVE            1
  12. #define PUSH            2
  13. #define SAVE            3
  14. #define UNSAVE          4
  15. #define STOREMOVE       5
  16. #define STOREPUSH       6
  17.  
  18. /* This value merely needs to be greater than the longest possible path length
  19.  * making it the number of squares in the array seems a good bet. 
  20.  */
  21. #define BADMOVE MAXROW*MAXCOL
  22.  
  23. /* some simple checking macros to make sure certain moves are legal when 
  24.  * trying to do mouse based movement.
  25.  */
  26. #define ISPLAYER(x,y) ((map[x][y] == player) || (map[x][y] == playerstore))
  27. #define ISCLEAR(x,y) ((map[x][y] == ground) || (map[x][y] == store))
  28. #define ISPACKET(x,y) ((map[x][y] == packet) || (map[x][y] == save))
  29.  
  30. static XEvent xev;
  31. static POS tpos1, tpos2, lastppos, lasttpos1, lasttpos2;
  32. static char lppc, ltp1c, ltp2c;
  33. static short action, lastaction;
  34.  
  35. static Boolean shift = _false_;
  36. static Boolean cntrl = _false_;
  37. static KeySym oldmove;
  38. static int findmap[MAXROW+1][MAXCOL+1];
  39.  
  40. #define MAXDELTAS 4
  41.  
  42. /** For stack saves. **/
  43. struct map_delta {
  44.     int x,y;
  45.     char oldchar, newchar;
  46. };
  47. struct move_r {
  48.     short px, py;
  49.     short moves, pushes, saved, ndeltas;
  50.     struct map_delta deltas[MAXDELTAS];
  51. };
  52. static struct move_r move_stack[STACKDEPTH];
  53. static int move_stack_sp; /* points to last saved move. If no saved move, -1 */
  54. static Map prev_map;
  55. static void RecordChange(void);
  56. static void UndoChange(void);
  57. static void InitMoveStack(void);
  58.  
  59. /* play a particular level.
  60.  * All we do here is wait for input, and dispatch to appropriate routines
  61.  * to deal with it nicely.
  62.  */
  63. short Play(void)
  64. {
  65.   short ret;
  66.   int bufs = 1;
  67.   char buf[1];
  68.   KeySym sym;
  69.   XComposeStatus compose;
  70.   
  71.   ClearScreen();
  72.   ShowScreen();
  73.   InitMoveStack();
  74.   ret = 0;
  75.   while (ret == 0) {
  76.     XNextEvent(dpy, &xev);
  77.     switch(xev.type) {
  78.       case Expose:
  79.     /* Redisplaying is pretty cheap, so I don't care too much about it */
  80.     RedisplayScreen();
  81.     break;
  82.       case ButtonPress:
  83.     switch(xev.xbutton.button) {
  84.       case Button1:
  85.         /* move the man to where the pointer is. */
  86.         MoveMan(xev.xbutton.x, xev.xbutton.y);
  87.         RecordChange();
  88.         break;
  89.       case Button2:
  90.         /* Push a ball */
  91.         PushMan(xev.xbutton.x, xev.xbutton.y);
  92.         RecordChange();
  93.         break;
  94.       case Button3:
  95.         /* undo last move */
  96.         UndoChange();
  97.         ShowScreen();
  98.         break;
  99.       default:
  100.         /* I'm sorry, you win the irritating beep for your efforts. */
  101.         HelpMessage();
  102.         break;
  103.     }
  104.     RedisplayScreen();
  105.     break;
  106.       case KeyPress:
  107.     buf[0] = '\0';
  108.     (void) XLookupString(&xev.xkey, buf, bufs, &sym, &compose);
  109.     cntrl = (xev.xkey.state & ControlMask) ? _true_ : _false_;
  110.     shift = (xev.xkey.state & ShiftMask) ? _true_ : _false_;
  111.     switch(sym) {
  112.       case XK_q:
  113.         /* q is for quit */
  114.         if(!cntrl)
  115.           ret = E_ENDGAME;
  116.         break;
  117.       case XK_s:
  118.         /* save */
  119.         if(!cntrl) {
  120.           ret = SaveGame();
  121.           if(ret == 0)
  122.         ret = E_SAVED;
  123.         }
  124.         break;
  125.       case XK_question:
  126.         /* help */
  127.         if(!cntrl) {
  128.           ShowHelp();
  129.           RedisplayScreen();
  130.         }
  131.         break;
  132.       case XK_r:
  133.         /* ^R refreshes the screen */
  134.         if(cntrl)
  135.           RedisplayScreen();
  136.         break;
  137.       case XK_U:
  138.         /* Do a full screen reset */
  139.         if(!cntrl) {
  140.           moves = pushes = 0;
  141.           ret = ReadScreen();
  142.           InitMoveStack();
  143.           if(ret == 0) {
  144.         ShowScreen();
  145.           }
  146.         }
  147.         break;
  148.       case XK_u:
  149.         if(!cntrl) {
  150.         UndoChange();
  151.         ShowScreen();
  152.         }
  153.         break;
  154.       case XK_k:
  155.       case XK_K:
  156.       case XK_Up:
  157.       case XK_j:
  158.       case XK_J:
  159.       case XK_Down:
  160.       case XK_l:
  161.       case XK_L:
  162.       case XK_Right:
  163.       case XK_h:
  164.       case XK_H:
  165.       case XK_Left:
  166.         /* A move, A move!! we have a MOVE!! */
  167.         MakeMove(sym);
  168.         RedisplayScreen();
  169.         RecordChange();
  170.         break;
  171.       default:
  172.         /* I ONLY want to beep if a key was pressed.  Contrary to what
  173.          * X11 believes, SHIFT and CONTROL are NOT keys
  174.          */
  175.             if(buf[0]) {
  176.           HelpMessage();
  177.         }
  178.         break;
  179.     }
  180.     break;
  181.       case ClientMessage:
  182.     {
  183.         XClientMessageEvent *cm = (XClientMessageEvent *)&xev;
  184.         if (cm->message_type == wm_protocols &&
  185.         cm->data.l[0] == wm_delete_window)
  186.           ret = E_ENDGAME;
  187.     }
  188.     break;
  189.       default:
  190.     break;
  191.     }
  192.     /* if we solved a level, set it up so we get some score! */
  193.     if((ret == 0) && (packets == savepack)) {
  194.       scorelevel = level;
  195.       scoremoves = moves;
  196.       scorepushes = pushes;
  197.       break;
  198.     }
  199.   }
  200.   return ret;
  201. }
  202.  
  203. /* Perform a user move based on the key in "sym". */
  204. void MakeMove(KeySym sym)
  205. {
  206.   do {
  207.     action = TestMove(sym);
  208.     if(action != 0) {
  209.       lastaction = action;
  210.       lastppos.x = ppos.x;
  211.       lastppos.y = ppos.y;
  212.       lppc = map[ppos.x][ppos.y];
  213.       lasttpos1.x = tpos1.x;
  214.       lasttpos1.y = tpos1.y;
  215.       ltp1c = map[tpos1.x][tpos1.y];
  216.       lasttpos2.x = tpos2.x;
  217.       lasttpos2.y = tpos2.y;
  218.       ltp2c = map[tpos2.x][tpos2.y];
  219.       DoMove(lastaction);
  220.       /* store the current keysym so we can do the repeat. */
  221.       oldmove = sym;
  222.     }
  223.   } while ((action != 0) && (packets != savepack) && (shift || cntrl));
  224. }
  225.  
  226. /* make sure a move is valid and if it is, return type of move */
  227. short TestMove(KeySym action)
  228. {
  229.   short ret;
  230.   char tc;
  231.   Boolean stop_at_object;
  232.  
  233.   stop_at_object = cntrl;
  234.  
  235.   if((action == XK_Up) || (action == XK_k) || (action == XK_K)) {
  236.     tpos1.x = ppos.x-1;
  237.     tpos2.x = ppos.x-2;
  238.     tpos1.y = tpos2.y = ppos.y;
  239.   }
  240.   if((action == XK_Down) || (action == XK_j) || (action == XK_J)) {
  241.     tpos1.x = ppos.x+1;
  242.     tpos2.x = ppos.x+2;
  243.     tpos1.y = tpos2.y = ppos.y;
  244.   }
  245.   if((action == XK_Left) || (action == XK_h) || (action == XK_H)) {
  246.     tpos1.y = ppos.y-1;
  247.     tpos2.y = ppos.y-2;
  248.     tpos1.x = tpos2.x = ppos.x;
  249.   }
  250.   if((action == XK_Right) || (action == XK_l) || (action == XK_L)) {
  251.     tpos1.y = ppos.y+1;
  252.     tpos2.y = ppos.y+2;
  253.     tpos1.x = tpos2.x = ppos.x;
  254.   }
  255.   tc = map[tpos1.x][tpos1.y];
  256.   if((tc == packet) || (tc == save)) {
  257.     if(!stop_at_object) {
  258.       if(map[tpos2.x][tpos2.y] == ground)
  259.     ret = (tc == save) ? UNSAVE : PUSH;
  260.       else if(map[tpos2.x][tpos2.y] == store)
  261.     ret = (tc == save) ? STOREPUSH : SAVE;
  262.       else
  263.     ret = 0;
  264.     } else
  265.       ret = 0;
  266.   } else if(tc == ground)
  267.     ret = MOVE;
  268.   else if(tc == store)
  269.     ret = STOREMOVE;
  270.   else
  271.     ret = 0;
  272.   return ret;
  273. }
  274.  
  275. /* actually update the internal map with the results of the move */
  276. void DoMove(short moveaction)
  277. {
  278.   map[ppos.x][ppos.y] = (map[ppos.x][ppos.y] == player) ? ground : store;
  279.   switch( moveaction) {
  280.     case MOVE:
  281.       map[tpos1.x][tpos1.y] = player;
  282.       break;
  283.     case STOREMOVE:
  284.       map[tpos1.x][tpos1.y] = playerstore;
  285.       break;
  286.     case PUSH:
  287.       map[tpos2.x][tpos2.y] = map[tpos1.x][tpos1.y];
  288.       map[tpos1.x][tpos1.y] = player;
  289.       pushes++;
  290.       break;
  291.     case UNSAVE:
  292.       map[tpos2.x][tpos2.y] = packet;
  293.       map[tpos1.x][tpos1.y] = playerstore;
  294.       pushes++;
  295.       savepack--;
  296.       break;
  297.     case SAVE:
  298.       map[tpos2.x][tpos2.y] = save;
  299.       map[tpos1.x][tpos1.y] = player;
  300.       savepack++;
  301.       pushes++;
  302.       break;
  303.     case STOREPUSH:
  304.       map[tpos2.x][tpos2.y] = save;
  305.       map[tpos1.x][tpos1.y] = playerstore;
  306.       pushes++;
  307.       break;
  308.   }
  309.   moves++;
  310.   DisplayMoves();
  311.   DisplayPushes();
  312.   DisplaySave();
  313.   MapChar(map[ppos.x][ppos.y], ppos.x, ppos.y, 1);
  314.   MapChar(map[tpos1.x][tpos1.y], tpos1.x, tpos1.y, 1);
  315.   MapChar(map[tpos2.x][tpos2.y], tpos2.x, tpos2.y, 1);
  316.   ppos.x = tpos1.x;
  317.   ppos.y = tpos1.y;
  318.   SyncScreen();
  319. }
  320.  
  321. /* undo the most recently done move */
  322. void UndoMove(void)
  323. {
  324.   map[lastppos.x][lastppos.y] = lppc;
  325.   map[lasttpos1.x][lasttpos1.y] = ltp1c;
  326.   map[lasttpos2.x][lasttpos2.y] = ltp2c;
  327.   ppos.x = lastppos.x;
  328.   ppos.y = lastppos.y;
  329.   switch( lastaction) {
  330.     case MOVE:
  331.       moves--;
  332.       break;
  333.     case STOREMOVE:
  334.       moves--;
  335.       break;
  336.     case PUSH:
  337.       moves--;
  338.       pushes--;
  339.       break;
  340.     case UNSAVE:
  341.       moves--;
  342.       pushes--;
  343.       savepack++;
  344.       break;
  345.     case SAVE:
  346.       moves--;
  347.       pushes--;
  348.       savepack--;
  349.       break;
  350.     case STOREPUSH:
  351.       moves--;
  352.       pushes--;
  353.       break;
  354.   }
  355.   DisplayMoves();
  356.   DisplayPushes();
  357.   DisplaySave();
  358.   MapChar(map[ppos.x][ppos.y], ppos.x, ppos.y, 1);
  359.   MapChar(map[lasttpos1.x][lasttpos1.y], lasttpos1.x, lasttpos1.y, 1);
  360.   MapChar(map[lasttpos2.x][lasttpos2.y], lasttpos2.x, lasttpos2.y, 1);
  361.   SyncScreen();
  362. }
  363.  
  364. /* Function used by the help pager.  We ONLY want to flip pages if a key
  365.  * key is pressed.. We want to exit the help pager if ENTER is pressed.
  366.  * As above, <shift> and <control> and other such fun keys are NOT counted
  367.  * as a keypress.
  368.  */
  369. Boolean WaitForEnter(void)
  370. {
  371.   KeySym keyhit;
  372.   char buf[1];
  373.   int bufs = 1;
  374.   XComposeStatus compose;
  375.  
  376.   while (1) {
  377.     XNextEvent(dpy, &xev);
  378.     switch(xev.type) {
  379.       case Expose:
  380.     RedisplayScreen();
  381.     break;
  382.       case KeyPress:
  383.     buf[0] = '\0';
  384.     XLookupString(&xev.xkey, buf, bufs, &keyhit, &compose);
  385.     if(buf[0]) {
  386.       return (keyhit == XK_Return) ? _true_ : _false_;
  387.     }
  388.     break;
  389.       default:
  390.     break;
  391.     }
  392.   }
  393. }
  394.  
  395. /* find the shortest path to the target via a fill search algorithm */
  396. void FindTarget(int px, int py, int pathlen)
  397. {
  398.   if(!(ISCLEAR(px, py) || ISPLAYER(px, py)))
  399.     return;
  400.   if(findmap[px][py] <= pathlen)
  401.     return;
  402.  
  403.   findmap[px][py] = pathlen++;
  404.  
  405.   if((px == ppos.x) && (py == ppos.y))
  406.     return;
  407.  
  408.   FindTarget(px - 1, py, pathlen);
  409.   FindTarget(px + 1, py, pathlen);
  410.   FindTarget(px, py - 1, pathlen);
  411.   FindTarget(px, py + 1, pathlen);
  412. }
  413.  
  414. /* Do all the fun movement stuff with the mouse */
  415. void MoveMan(int mx, int my)
  416. {
  417.   int x, y;
  418.  
  419.   shift = cntrl = _false_;
  420.  
  421.   /* reverse the screen coords to get the internal coords (yes, I know this 
  422.    * should be fixed) RSN */
  423.   y = wX(mx);
  424.   x = wY(my);
  425.  
  426.   /* make sure we are within the bounds of the array */
  427.   if((x < 0) || (x > MAXROW) || (y < 0) || (y > MAXCOL)) {
  428.     HelpMessage();
  429.     return;
  430.   }
  431.  
  432.   if(ISPACKET(x, y)) {
  433.     HelpMessage();
  434.     return;
  435.   }
  436.   /* if we clicked on the player or a wall (or an object but that was already
  437.    * handled) the we don't want to move.
  438.    */
  439.   if(!ISCLEAR(x, y)) {
  440.     HelpMessage();
  441.     return;
  442.   }
  443.   if (!RunTo(x, y)) HelpMessage();
  444. }
  445.  
  446. /* Return whether (x,y) is on the board */
  447. Boolean ValidPosn(int x, int y)
  448. {
  449.     return (x >= 0) && (x <= MAXROW) && (y >= 0) && (y <= MAXCOL);
  450. }
  451.  
  452. /* 
  453.    Find the object at a position orthogonal to (x, y) that is
  454.    closest to (ppos.x, ppos.y), is separated from (x,y) only
  455.    by empty spaces, and has the player or an empty space on the far
  456.    side of (x,y), and is not directly opposite the destination space
  457.    from the player; place its coordinates in (*ox, *oy) and return _true_.
  458.    If no such object exists, return _false_.
  459. */
  460. Boolean FindOrthogonalObject(int x, int y, int *ox, int *oy)
  461. {
  462.     int dir;
  463.     int bestdist = BADMOVE;
  464.     Boolean foundOne = _false_;
  465.     for (dir = 0; dir < 4; dir++) {
  466.     int dx, dy, x1, y1, dist;
  467.     switch(dir) {
  468.         default: dx = 1; dy = 0; break; /* use "default" to please gcc */
  469.         case 1: dx = -1; dy = 0; break;
  470.         case 2: dx = 0; dy = 1; break;
  471.         case 3: dx = 0; dy = -1; break;
  472.     }
  473.     if ((ppos.x == x && ((ppos.y - y)*dy) < 0)
  474.         || (ppos.y == y && ((ppos.x - x)*dx) < 0)) continue;
  475.     /* Eliminate case where player would push in the opposite
  476.        direction. */
  477.     x1 = x; y1 = y;
  478.         while (ValidPosn(x1, y1)) {
  479.         x1 += dx; y1 += dy;
  480.         dist = abs(ppos.x - x1) + abs(ppos.y - y1);
  481.         if (dist <= bestdist && ISPACKET(x1, y1) &&
  482.         (ISPLAYER(x1 + dx, y1 + dy) || ISCLEAR(x1 + dx, y1 + dy))) {
  483.         if (dist < bestdist) {
  484.             bestdist = dist;
  485.             *ox = x1;
  486.             *oy = y1;
  487.             foundOne = _true_;
  488.             break;
  489.         } else {
  490.             foundOne = _false_; /* found one that was just as good */
  491.         }
  492.         }
  493.         if (!ISCLEAR(x1, y1)) break;
  494.     }
  495.     }
  496.     return foundOne ? _true_ : _false_ ;
  497. }
  498.  
  499. #define DEBUGPUSH 0
  500.  
  501. /* Push a nearby stone to the position indicated by (mx, my). */
  502. void PushMan(int mx, int my)
  503. {
  504.   int i, x, y, ox, oy, dist;
  505.  
  506.   shift = cntrl = _false_;
  507.  
  508.   /* reverse the screen coords to get the internal coords (yes, I know this 
  509.    * should be fixed) RSN */
  510.   y = wX(mx);
  511.   x = wY(my);
  512.  
  513.   /* make sure we are within the bounds of the array */
  514.   if(!ValidPosn(x,y)) {
  515.     HelpMessage();
  516. #if DEBUGPUSH
  517.     printf("Outside array\n");
  518. #endif
  519.     return;
  520.   }
  521.  
  522.   /* if we are clicking on an object, and are right next to it, we want to
  523.    * move the object.
  524.    */
  525.   if(ISPACKET(x, y)) {
  526.     if(ISPLAYER(x - 1, y))
  527.       MakeMove(XK_Down);
  528.     else if(ISPLAYER(x + 1, y))
  529.       MakeMove(XK_Up);
  530.     else if(ISPLAYER(x, y - 1))
  531.       MakeMove(XK_Right);
  532.     else if(ISPLAYER(x, y + 1))
  533.       MakeMove(XK_Left);
  534.     else {
  535.       /* we weren't right next to the object */
  536.       HelpMessage();
  537.       return;
  538.     }
  539.     return;
  540.   }
  541.  
  542.   /* if we clicked on the player or a wall (or an object but that was already
  543.    * handled) the we don't want to move.
  544.    */
  545.   if(!ISCLEAR(x, y)) {
  546.     HelpMessage();
  547. #if DEBUGPUSH
  548.     printf("Not a clear space\n"); */
  549. #endif
  550.     return;
  551.   }
  552.  
  553. #if 0
  554.   if (abs(x - ppos.x) < 2 && abs(ppos.y - y) < 2) {
  555. #if DEBUGPUSH
  556.     printf("Too close to destination (%d, %d)\n",
  557.         abs(x - ppos.x) , abs(y - ppos.y));
  558. #endif
  559.     HelpMessage();
  560.     /* Player must be sufficiently far from the destination that there
  561.        can be no ambiguity about which stone to push */
  562.     return;
  563.   }
  564. #endif
  565.  
  566.   if (!FindOrthogonalObject(x, y, &ox, &oy)) {
  567.     HelpMessage();
  568. #if DEBUGPUSH
  569.     printf("Can't find packet\n");
  570. #endif
  571.     return;
  572.   }
  573.  
  574.   assert(x == ox || y == oy);
  575.   dist = abs(ox - x) + abs(oy - y);
  576.  
  577.   if (x > ox) ox--;
  578.   if (x < ox) ox++;
  579.   if (y > oy) oy--;
  580.   if (y < oy) oy++;
  581.  
  582.   /* (ox,oy) now denotes the place we need to run to to be able to push */
  583.  
  584.   if (ox != ppos.x || oy != ppos.y) {
  585.       if (!ISCLEAR(ox, oy)) {
  586. #if DEBUGPUSH
  587.     printf("Can't move into an occupied space! (%d,%d)\n",
  588.         ox - ppos.x, oy - ppos.y);
  589. #endif
  590.     HelpMessage();
  591.     return;
  592.       }
  593.       if (!RunTo(ox, oy)) {
  594.     HelpMessage();
  595. #if DEBUGPUSH
  596.     printf("Can't get in position to push\n"); */
  597. #endif
  598.     return;
  599.       }
  600.   }
  601.   assert(ppos.x == ox && ppos.y == oy);
  602.  
  603.   for (i = 0; i < dist; i++) {
  604.     if (ppos.x < x) MakeMove(XK_Down);
  605.     if (ppos.x > x) MakeMove(XK_Up);
  606.     if (ppos.y < y) MakeMove(XK_Right);
  607.     if (ppos.y > y) MakeMove(XK_Left);
  608.   }
  609. }
  610.  
  611. /* Move the player to the position (x,y), if possible. Return _true_
  612.    iff successful. The position (x,y) must be clear.
  613. */
  614. Boolean RunTo(int x, int y)
  615. {
  616.   int i,j,cx,cy;
  617.   /* Fill the trace map */
  618.   for(i = 0; i <= MAXROW; i++)
  619.     for (j = 0; j <= MAXCOL; j++)
  620.       findmap[i][j] = BADMOVE;
  621.   /* flood fill search to find a shortest path to the push point. */
  622.   FindTarget(x, y, 0);
  623.  
  624.   /* if we didn't make it back to the players position, there is no valid path
  625.    * to that place.
  626.    */
  627.   if(findmap[ppos.x][ppos.y] == BADMOVE) {
  628.     return _false_;
  629.   } else {
  630.     /* we made it back, so let's walk the path we just built up */
  631.     cx = ppos.x;
  632.     cy = ppos.y;
  633.     while(findmap[cx][cy]) {
  634.       if(findmap[cx - 1][cy] == (findmap[cx][cy] - 1)) {
  635.     MakeMove(XK_Up);
  636.     cx--;
  637.       } else if(findmap[cx + 1][cy] == (findmap[cx][cy] - 1)) {
  638.     MakeMove(XK_Down);
  639.     cx++;
  640.       } else if(findmap[cx][cy - 1] == (findmap[cx][cy] - 1)) {
  641.     MakeMove(XK_Left);
  642.     cy--;
  643.       } else if(findmap[cx][cy + 1] == (findmap[cx][cy] - 1)) {
  644.     MakeMove(XK_Right);
  645.     cy++;
  646.       } else {
  647.     /* if we get here, something is SERIOUSLY wrong, so we should abort */
  648.     abort();
  649.       }
  650.     }
  651.   }
  652.   return _true_;
  653. }
  654.  
  655.  
  656. static void InitMoveStack()
  657. {
  658.     move_stack_sp = -1;
  659.     move_stack[0].moves = moves;
  660.     move_stack[0].pushes = moves;
  661.     move_stack[0].saved = savepack;
  662.     memcpy(prev_map, map, sizeof(map));
  663. }
  664.  
  665. /* Add a record to the move stack that records the changes since the
  666.    last map state (which is stored in "prev_map"). Update "prev_map"
  667.    to contain the current map so the next call to "RecordChange()"
  668.    will perform correctly.
  669.    
  670.    If the stack runs out of space, dump the oldest half of the
  671.    saved moves and continue. Undoing past that point will jump
  672.    back to the beginning of the level. If the user is using the
  673.    mouse or any skill, should never happen.
  674. */
  675. static void RecordChange()
  676. {
  677.     struct move_r *r = &move_stack[++move_stack_sp];
  678.     int x,y, ndeltas = 0;
  679.     assert(move_stack_sp < STACKDEPTH);
  680.     if (move_stack_sp == STACKDEPTH - 1) {
  681.     int shift = STACKDEPTH/2;
  682.     memcpy(&move_stack[0], &move_stack[shift],
  683.           sizeof(struct move_r) * (STACKDEPTH - shift));
  684.     move_stack_sp -= shift;
  685.     r -= shift;
  686.     }
  687.     r[1].moves = moves;
  688.     r[1].pushes = pushes;
  689.     r[1].saved = savepack;
  690.     r[1].px = ppos.x;
  691.     r[1].py = ppos.y;
  692.     for (x = 0; x <= MAXROW; x++) {
  693.     for (y = 0; y <= MAXROW; y++) {
  694.         if (map[x][y] != prev_map[x][y]) {
  695.         assert(ndeltas < MAXDELTAS);
  696.         r->deltas[ndeltas].x = x;
  697.         r->deltas[ndeltas].y = y;
  698.         r->deltas[ndeltas].newchar = map[x][y];
  699.         r->deltas[ndeltas].oldchar = prev_map[x][y];
  700.         ndeltas++;
  701. #if 0
  702.         printf("Change (%d,%d) %c->%c\n", x, y, prev_map[x][y],
  703.                map[x][y]);
  704. #endif
  705.         }
  706.     }
  707.     }
  708.     r->ndeltas = ndeltas;
  709.     if (ndeltas == 0) {
  710.     move_stack_sp--; /* Why push an identical entry? */
  711.     }
  712.     memcpy(prev_map, map, sizeof(map));
  713. }
  714.  
  715. static void UndoChange()
  716. {
  717.     if (move_stack_sp <= 0) {
  718.     int ret;
  719.     InitMoveStack();
  720.     ret = ReadScreen();
  721.     moves = pushes = 0;
  722.     if (ret) {
  723.         fprintf(stderr, "Can't read screen file\n");
  724.         exit(-1);
  725.     }
  726.     } else {
  727.     struct move_r *r = &move_stack[move_stack_sp];
  728.     int i;
  729.     moves = r->moves;
  730.     pushes = r->pushes;
  731.     savepack = r->saved;
  732.     ppos.x = r->px;
  733.     ppos.y = r->py;
  734.     for (i = 0; i<r->ndeltas; i++) {
  735. #if 0
  736.         printf("Applying reverse change: (%d,%d) %c->%c\n",
  737.         r->deltas[i].x, r->deltas[i].y,
  738.            map[r->deltas[i].x][r->deltas[i].y], r->deltas[i].oldchar);
  739. #endif
  740.         map[r->deltas[i].x][r->deltas[i].y] = r->deltas[i].oldchar;
  741.     }
  742.     move_stack_sp--;
  743.     memcpy(prev_map, map, sizeof(map));
  744.     }
  745. }
  746.