1 /* -*- c++ -*-
2 FILE: History.cpp
3 RCS REVISION: $Revision: 1.29 $
4 
5 COPYRIGHT: (c) 1999 -- 2003 Melinda Green, Don Hatch, and Jay Berkenbilt - Superliminal Software
6 
7 LICENSE: Free to use and modify for non-commercial purposes as long as the
8     following conditions are adhered to:
9     1) Obvious credit for the source of this code and the designs it embodies
10        are clearly made, and
11     2) Ports and derived versions of 4D Magic Cube programs are not distributed
12        without the express written permission of the authors.
13 
14 DESCRIPTION:
15     Implementation of the History class
16 */
17 
18 #include "MagicCube.h"
19 #include "History.h"
20 #include "Polymgr.h"
21 #include "Math4d.h"
22 #include "Preferences.h"
23 #include <ctype.h>
24 
25 
History(Preferences & p,PolygonManager4D * pmgr)26 History::History(Preferences& p, PolygonManager4D *pmgr) :
27     preferences(p)
28 {
29     length = p.getLength();
30     polymgr = pmgr;
31     debug = preferences.getBoolProperty(M4D_HISTORY_DEBUG);
32     current = first = last = (struct historynode *)NULL;
33 }
34 
~History()35 History::~History()
36 {
37     clear();
38 }
39 
reset(int new_length)40 void History::reset(int new_length)
41 {
42     if (new_length != -1)
43     {
44         length = new_length;
45     }
46     clear();
47 }
48 
isSane()49 int History::isSane()
50 {
51     int found_current = 0;
52     int ngrips = NFACES * 3 * 3 * 3;
53     struct historynode *node;
54 
55     assert((first == 0) == (last == 0));
56 
57     if (first)
58     {
59         for (node = first; node; node = node->next)
60         {
61             if (node->prev)
62             {
63                 assert(node->prev->next == node);
64             }
65             else
66             {
67                 assert(first == node);
68             }
69             if (node->next)
70             {
71                 assert(node->next->prev == node);
72             }
73             else
74             {
75                 assert(last == node);
76             }
77 
78             if (node == current)
79             {
80                 assert(node->stickerid >= 0);
81                 found_current = 1;
82             }
83 
84             if (node->stickerid >= 0)
85             {
86                 assert(INRANGE(0 <=, node->stickerid, <ngrips));
87                 assert(node->dir == CCW || node->dir == CW);
88             }
89         }
90     }
91 
92     assert(found_current == (current != 0));
93 
94     return 1;
95 }
96 
97 
deleteNode(struct historynode * node)98 void History::deleteNode( struct historynode *node)
99 {
100     if (!node)
101         return;
102     if (current == node)
103         current = node->next;
104 
105     if (node->prev)
106         node->prev->next = node->next;
107     else
108         first = node->next;
109     if (node->next)
110         node->next->prev = node->prev;
111     else
112         last = node->prev;
113     delete node;
114 }
115 
insertNode(struct historynode * node_to_insert_before,int stickerid,int dir,int slicesmask,int mark)116 void History::insertNode(
117         struct historynode *node_to_insert_before,
118         int stickerid, int dir, int slicesmask, int mark)
119 {
120     historynode *temp;
121 
122     temp = new historynode;
123     temp->stickerid = stickerid;
124     temp->dir = dir;
125     temp->slicesmask = slicesmask;
126     temp->mark = mark;
127 
128     temp->prev = (node_to_insert_before ?
129                   node_to_insert_before->prev : last);
130     temp->next = node_to_insert_before;
131 
132     if (temp->next)
133         temp->next->prev = temp;
134     else
135         last = temp;
136 
137     if (temp->prev)
138         temp->prev->next = temp;
139     else
140         first = temp;
141 }
142 
deleteLast()143 void History::deleteLast()
144 {
145     deleteNode(last);
146 }
147 
clear()148 void History::clear()
149 {
150     while (first)
151         deleteLast();
152 }
153 
append(int stickerid,int dir,int slicesmask)154 void History::append(int stickerid, int dir, int slicesmask)
155 {
156     struct historynode *node = getPrevious();
157     if(node != NULL // there is a previous twist
158         && node->stickerid == stickerid // same axis
159         && node->slicesmask == slicesmask // same slices
160         && node->dir == -dir) // but in *opposite* direction
161     {
162         struct stickerspec dummyss;
163         int dummyint;
164         undo(&dummyss, &dummyint, &dummyint); // just back the move out rather than append an inverse move
165     }
166     else
167         insertNode(NULL, stickerid, dir, slicesmask, 0);
168 }
169 
mark(int mark)170 void History::mark(int mark)
171 {
172     insertNode(current, -1, 0, 0, mark);
173 }
174 
175 
176 /*
177  * If there is a "current", delete it and everything after it.
178  */
truncate()179 void History::truncate()
180 {
181     while (current)
182         deleteLast();
183     // Special case: If we are at a macro open mark, eat it.
184 
185     // FIX THIS -- see comments in
186     // EventHandler::applyMacroCBAfterGotRefStickers about how this
187     // makes things awkward at time of application of macro.  The
188     // marking of macros is a little problematic implemented this way,
189     // but the current kludges seem to work right.  One needs to be
190     // able to undo/redo past macros in fast automove mode and to have
191     // no stray m[ in the file after doing a move following undoing a
192     // macro or after applying a macro after undoing a macro.
193     if (atMacroOpen())
194     {
195         deleteLast();
196     }
197 }
198 
199 /*
200  * Set current to be the beginning of the list.
201  */
goToBeginning()202 void History::goToBeginning()
203 {
204     current = first;
205 }
206 
goToEnd()207 void History::goToEnd()
208 {
209     current = NULL;
210 }
211 
212 /*
213  * Put a single move into the history.
214  * This clears the history after
215  * the current point, so a "redo" is impossible afterwards.
216  */
apply(struct stickerspec * sticker,int dir,int slicesmask)217 void History::apply(struct stickerspec *sticker, int dir, int slicesmask)
218 {
219     truncate();
220     append(sticker->id_within_cube, dir, slicesmask);
221 }
222 
size()223 int History::size()
224 {
225     struct historynode *node;
226     int         count = 0;
227     for (node = first; node; node = node->next)
228         count++;
229     return count;
230 }
231 
232 /*
233  * Truncate (i.e. delete everything past current),
234  * remove non-moves (marks),
235  * merge rotates,
236  * put rotates before the twists,
237  * put opposite-face twists in canonical order,
238  * merge same-face twists.
239  * This is usually done in preparation for a "cheat" solve.
240  *
241  * FIX THIS-- this should be done on the fly, as the cheat-solve is happening
242  * (doing it like this causes a long wait if the history is long).
243  */
compress()244 void History::compress()
245 {
246     struct historynode *node, **nodeptr;
247     struct historynode *first_on_this_face, *past_last_on_this_face;
248     int         current_matrix[4][4], incmat[4][4], testmat[4][4],
249         newcoords[4];
250     struct stickerspec grip;
251     int         face, dir, thisface, nextface, temp;
252 
253     /*
254      * Truncate
255      */
256     truncate();
257 
258     /*
259      * Remove all non-moves
260      */
261     for (nodeptr = &first; *nodeptr;)
262     {
263         if ((*nodeptr)->stickerid == -1)
264             deleteNode(*nodeptr);
265         else
266             nodeptr = &(*nodeptr)->next;
267     }
268 
269     /*
270      * Sweep all rotates to beginning
271      */
272     IDENTMAT4(current_matrix);
273     for (nodeptr = &last; *nodeptr;)
274     {
275         if ((1 << length) & ~(*nodeptr)->slicesmask)
276         {
277             /*
278              * It's a twist (some slices stay and some move).
279              * Apply the current matrix to the coords of
280              * the grip.
281              */
282             grip.id_within_cube = (*nodeptr)->stickerid;
283             polymgr->fillStickerspecFromIdAndLength(&grip, 3);
284             VXM4i(grip.coords, grip.coords, current_matrix);
285             polymgr->fillStickerspecFromCoordsAndLength(&grip, 3);
286             (*nodeptr)->stickerid = grip.id_within_cube;
287 
288             nodeptr = &(*nodeptr)->prev;
289         }
290         else
291         {
292             /*
293              * It's a rotate.  Just preconcatenate it
294              * to the current matrix.
295              */
296             grip.id_within_cube = (*nodeptr)->stickerid;
297             polymgr->fillStickerspecFromIdAndLength(&grip, 3);
298             real angle = polymgr->getTwistTotalAngle(grip.dim, (*nodeptr)->dir);
299             Math4d::get4dTwistMat(grip.coords, angle, incmat);
300             MXM4i(current_matrix, incmat, current_matrix);
301 
302             deleteNode(*nodeptr);
303         }
304     }
305     /*
306      * The proper thing to do now would be to add the rotates
307      * to the beginning of the history, but I'm not bothering,
308      * since the only thing this function is used for anyway
309      * is the cheat-solve.
310      */
311 
312     /*
313      * Put opposite-face twists in canonical order
314      */
315     for (node = first; node;)
316     {
317         if (node->slicesmask == 1 &&
318             node->next && node->next->slicesmask == 1)
319         {
320             thisface = PolygonManager4D::faceOfGrip(node->stickerid);
321             nextface = PolygonManager4D::faceOfGrip(node->next->stickerid);
322             if (nextface < thisface &&
323                 nextface == polymgr->oppositeFace(thisface))
324             {
325                 SWAP(node->stickerid, node->next->stickerid, temp);
326                 SWAP(node->dir, node->next->dir, temp);
327                 SWAP(node->slicesmask, node->next->slicesmask, temp);
328                 if (node->prev)
329                     node = node->prev;
330             }
331             else
332                 node = node->next;
333         }
334         else
335             node = node->next;
336     }
337 
338     /*
339      * Merge same-face twists
340      */
341     for (first_on_this_face = first; first_on_this_face;)
342     {
343         if (first_on_this_face->slicesmask == 1)
344         {
345             face = PolygonManager4D::faceOfGrip(first_on_this_face->stickerid);
346             past_last_on_this_face = first_on_this_face->next;
347             while (past_last_on_this_face &&
348                    past_last_on_this_face->slicesmask == 1 &&
349                    PolygonManager4D::faceOfGrip(
350                        past_last_on_this_face->stickerid) == face)
351             {
352                 past_last_on_this_face = past_last_on_this_face->next;
353             }
354 
355             if (past_last_on_this_face != first_on_this_face->next)
356             {
357                 /*
358                  * There is more than one twist on this face.
359                  * Concatenate together all the matrices of these
360                  * twists, and then figure out how to accomplish it
361                  * with one or two twists on a single grip.
362                  */
363                 IDENTMAT4(current_matrix);
364                 for (node = first_on_this_face;
365                      node != past_last_on_this_face; node = node->next)
366                 {
367                     grip.id_within_cube = node->stickerid;
368                     polymgr->fillStickerspecFromIdAndLength(&grip, 3);
369                     real angle = polymgr->getTwistTotalAngle(grip.dim, node->dir);
370                     Math4d::get4dTwistMat(grip.coords, angle, incmat);
371                     MXM4i(current_matrix, current_matrix, incmat);
372                 }
373 
374                 /*
375                  * We now have all the information we need;
376                  * delete the twists from the history
377                  */
378                 while (first_on_this_face != past_last_on_this_face)
379                 {
380                     struct historynode *temp = first_on_this_face;
381                     first_on_this_face = first_on_this_face->next;
382                     deleteNode(temp);
383                 }
384 
385                 /*
386                  * Find a sticker on this face that's not
387                  * moved by the matrix; that will be the grip of the
388                  * concatenated move.
389                  */
390                 grip.face = face;
391                 for (grip.id_within_face = 0;
392                      grip.id_within_face < 3 * 3 * 3; grip.id_within_face++)
393                 {
394                     polymgr->fillStickerspecFromFaceAndIdAndLength
395                         (&grip, 3);
396                     if (grip.dim >= 3)
397                         continue;
398                     VXM4(newcoords, grip.coords, current_matrix);
399                     if (EQVEC4(newcoords, grip.coords))
400                     {
401                         real        angle;
402                         /*
403                          * Found the right grip;
404                          * see if any of the following work:
405                          *  0 twists
406                          *  1 twist CW
407                          *  1 twist CCW
408                          *  2 twists in random direction
409                          */
410                         IDENTMAT4(testmat);
411                         if (EQMAT4(testmat, current_matrix))
412                         {
413                             /*
414                              * Result is 0 twists.
415                              */
416                             break;
417                         }
418                         angle = polymgr->getTwistTotalAngle(grip.dim, CCW);
419                         Math4d::get4dTwistMat(grip.coords, angle, testmat);
420                         if (EQMAT4(testmat, current_matrix))
421                         {
422                             /*
423                              * Result is 1 twist CCW.
424                              */
425                             insertNode(
426                                                    past_last_on_this_face,
427                                                    grip.id_within_cube,
428                                                    CCW, 1, 0);
429                             break;
430                         }
431                         angle = polymgr->getTwistTotalAngle(grip.dim, CW);
432                         Math4d::get4dTwistMat(grip.coords, angle, testmat);
433                         if (EQMAT4(testmat, current_matrix))
434                         {
435                             /*
436                              * Result is 1 twist CW.
437                              */
438                             insertNode(
439                                                    past_last_on_this_face,
440                                                    grip.id_within_cube,
441                                                    CW, 1, 0);
442                             break;
443                         }
444 
445                         dir = (rand() % 2 ? CW : CCW);
446                         angle = polymgr->getTwistTotalAngle(grip.dim, CCW);
447                         Math4d::get4dTwistMat(grip.coords, angle, testmat);
448                         MXM4i(testmat, testmat, testmat);
449                         if (EQMAT4(testmat, current_matrix))
450                         {
451                             /*
452                              * Result is 2 twists
453                              * in arbitrary direction.
454                              */
455                             insertNode(
456                                                    past_last_on_this_face,
457                                                    grip.id_within_cube,
458                                                    dir, 1, 0);
459                             insertNode(
460                                                    past_last_on_this_face,
461                                                    grip.id_within_cube,
462                                                    dir, 1, 0);
463                             break;
464                         }
465                         assert(0);
466                     }
467                 }
468                 assert(grip.id_within_face < 3 * 3 * 3);
469             }
470             first_on_this_face = past_last_on_this_face;
471         }
472         else
473             first_on_this_face = first_on_this_face->next;
474     }
475 }                               /* history_compress_history */
476 
getPrevious()477 struct History::historynode* History::getPrevious()
478 {
479     /*
480      * return most recent history node whether actual move or not
481      */
482     return (current ? current->prev : last);
483 }
484 
485 /*
486  * Back up one move in the history, returning a move
487  * that would undo the last move.
488  * Returns 1 on success, 0 if there is nothing to undo.
489  */
undo(struct stickerspec * sticker,int * Dir,int * Slicesmask)490 int History::undo(
491         struct stickerspec *sticker, /* OUTPUT */
492         int *Dir,  /* OUTPUT */
493         int *Slicesmask)   /* OUTPUT */
494 {
495     struct historynode *node;
496     /*
497      * search backwards to the next actual move
498      */
499     for (node = getPrevious(); node != NULL; node = node->prev)
500     {
501         if (node->stickerid != -1)
502             break;
503     }
504 
505     if (!node)
506         return 0;
507 
508     current = node;
509 
510     /* the following code is duplicated below */
511     sticker->id_within_cube = current->stickerid;
512     polymgr->fillStickerspecFromIdAndLength(sticker, 3);
513     *Dir = current->dir;
514     *Slicesmask = current->slicesmask;
515 
516     *Dir = -*Dir;
517 
518     if (debug)
519         printf("History size is now %d\n", size());
520 
521     return 1;
522 }
523 
524 
525 /*
526  * Go forward one move in the history, returning the move
527  * to redo.  This is only valid if a move was undone.
528  * Returns 1 on success, 0 if there is nothing to redo.
529  */
redo(struct stickerspec * sticker,int * Dir,int * Slicesmask)530 int History::redo(struct stickerspec *sticker, /* OUTPUT */
531                   int *Dir, /* OUTPUT */
532                   int *Slicesmask) /* OUTPUT */
533 {
534     if (!current)
535         return 0;
536 
537     /* the following code is duplicated above */
538     sticker->id_within_cube = current->stickerid;
539     polymgr->fillStickerspecFromIdAndLength(sticker, 3);
540     *Dir = current->dir;
541     *Slicesmask = current->slicesmask;
542 
543     current = current->next;
544     while (current && current->stickerid == -1)
545         current = current->next;
546 
547     return 1;
548     if (debug)
549         printf("History size is now %d\n", size());
550 }
551 
552 
goBackwardsTowardsMark(int mark,struct stickerspec * sticker,int * Dir,int * Slicesmask)553 int History::goBackwardsTowardsMark(int mark,
554                                     struct stickerspec *sticker, /* OUTPUT */
555                                     int *Dir, /* OUTPUT */
556                                     int *Slicesmask) /* OUTPUT */
557 {
558     struct historynode* node;
559     // Continue searching backwards for a potential undo
560     for (node = getPrevious(); node; node = node->prev)
561     {
562         if (node->stickerid == -1 && node->mark == mark)
563         {
564             if (!undo(sticker, Dir, Slicesmask))
565             {
566                 fprintf(stderr, "history_gotowards_mark: coudn't undo???\n");
567                 return 0;
568             }
569             return 1;
570         }
571     }
572     return -1;
573 }
574 
goForwardsTowardsMark(int mark,struct stickerspec * sticker,int * Dir,int * Slicesmask)575 int History::goForwardsTowardsMark(int mark,
576                                    struct stickerspec *sticker, /* OUTPUT */
577                                    int *Dir, /* OUTPUT */
578                                    int *Slicesmask) /* OUTPUT */
579 {
580     struct historynode* node;
581     // Search forwards for a potential redo
582     for (node = (current ? current : 0);
583          node; node = node->next)
584     {
585         if (node->stickerid == -1 && node->mark == mark)
586         {
587             if (!redo(sticker, Dir, Slicesmask))
588             {
589                 fprintf(stderr, "history_gotowards_mark: coudn't redo???\n");
590                 return 0;
591             }
592             return 1;
593         }
594     }
595     return -1;
596 }
597 
598 
599 /*
600  * Executes a history_undo or history_redo and returns 1 on success.
601  * Returns 0 if already at the mark, -1 if no such mark.  If
602  * forward_first is true and we are not at the mark, then search
603  * forward first and search backward only if the mark is not ahead of
604  * us.  Otherwise, search backward first and search forward only if
605  * the mark is not behind us.  Being able to choose which way to go
606  * first makes it useful to have multiple marks with the same
607  * identifier.
608  */
goTowardsMark(int mark,struct stickerspec * sticker,int * Dir,int * Slicesmask,bool forward_first)609 int History::goTowardsMark(int mark, struct stickerspec *sticker, /* OUTPUT */
610                            int *Dir, /* OUTPUT */
611                            int *Slicesmask, /* OUTPUT */
612                            bool forward_first)
613 {
614     int status = -1;
615     if (atMark(mark))
616     {
617         return 0;               /* already at the mark */
618     }
619     if (! forward_first)
620     {
621         status = this->goBackwardsTowardsMark(mark, sticker, Dir, Slicesmask);
622         if (status >= 0)
623         {
624             return status;
625         }
626     }
627     status = this->goForwardsTowardsMark(mark, sticker, Dir, Slicesmask);
628     if (status >= 0)
629     {
630         return status;
631     }
632     if (forward_first)
633     {
634         status = this->goBackwardsTowardsMark(mark, sticker, Dir, Slicesmask);
635     }
636     return status;
637 }
638 
639 /*
640  * Make a note that the puzzle is currently in a solved state
641  * so that hitting the "cheat" button will not make it go past
642  * this state.  Actually this is probably not necessary since
643  * the program can check by itself.
644  */
noteSolved()645 void History::noteSolved()
646 {
647     fprintf(stderr, "history_note_solved: not implemented\n");
648 }
649 
dump(FILE * fp)650 void History::dump( FILE *fp)
651 {
652     struct historynode *node;
653     int         nwritten, stickerid;
654     int         ngripsperface = 3 * 3 * 3;
655 
656     assert(isSane());
657     nwritten = 0;
658     for (node = first; node; node = node->next)
659     {
660         if (node == current)
661             fprintf(fp, "c ");
662         if (node->stickerid >= 0)
663         {
664             stickerid = node->stickerid;
665             /*
666              * If it's CW, use the opposite sticker.
667              */
668             if (node->dir == CW)
669                 stickerid = ROUNDDOWN(stickerid, ngripsperface) +
670                     ngripsperface - 1 - stickerid % ngripsperface;
671             fprintf(fp, "%d%d", stickerid / ngripsperface,
672                     stickerid % ngripsperface);
673             if (node->slicesmask != 1)
674                 fprintf(fp, ":%d", node->slicesmask);
675         }
676         else
677         {
678             fprintf(fp, "m%c", node->mark);
679         }
680         nwritten++;
681         if (node->next)
682         {
683             if (nwritten % 10 == 0)
684                 fprintf(fp, "\n");
685             else
686                 fprintf(fp, " ");
687         }
688     }
689     fprintf(fp, ".\n");
690 }
691 
692 #define lookc(fp) ungetc(getc(fp), fp)
693 
read(FILE * fp)694 int History::read( FILE *fp)
695 {
696     char        inchar;
697     int         c;
698     int         face, stickeronface, slicesmask;
699     int         ngripsperface = 3 * 3 * 3;
700     struct historynode **who_will_point_to_current = 0; /* BLEAH! */
701 
702     clear();
703 
704     while (1)
705     {
706         while ((c = getc(fp)) != EOF && isspace(c))
707             ;
708         if (c == EOF)
709             goto outahere;
710         if (c == '.')
711             break;
712         if (isdigit(c))
713         {
714             slicesmask = 1;
715             ungetc(c, fp);
716             if (fscanf(fp, "%1d%d:%d", &face, &stickeronface, &slicesmask) < 2)
717                 goto outahere;
718             append(
719                 face * ngripsperface + stickeronface,
720                 CCW, slicesmask);
721         }
722         else if (c == 'm')
723         {
724             if (fscanf(fp, "%c", &inchar) != 1)
725                 goto outahere;
726             mark((int)inchar);
727         }
728         else if (c == 's')
729         {
730             noteSolved();
731         }
732         else if (c == 'c')
733         {
734             who_will_point_to_current = (last ? &last->next : &first);
735         }
736         else
737             goto outahere;
738     }
739     if (who_will_point_to_current)
740         current = *who_will_point_to_current;
741     if (debug)
742         printf("History size is now %d\n", size());
743     return 1;
744     {
745     int status = read(fp);
746     if (!isSane())
747         fprintf(stderr, "Internal error: history is messed up.\n");
748     return status;
749     }
750 
751   outahere:
752     fprintf(stderr, "Error reading history-- no history read\n");
753     clear();
754     return 0;
755 }
756 
757 
countTwists()758 int History::countTwists()
759 {
760     int         result = 0;
761     struct historynode *cur_node;
762     for (cur_node = first; cur_node; cur_node = cur_node->next)
763     {
764         if (cur_node->stickerid >= 0)
765         {
766             ++result;
767         }
768     }
769     return result;
770 }
771 
atMark(int mark)772 int History::atMark(int mark)
773 {
774     struct historynode *node;
775     /*
776      * Go through all marks at the current position
777      */
778     for (node = (current ? current->prev : last);
779          node && node->stickerid == -1;
780          node = node->prev)
781     {
782         if (node->stickerid == -1 && node->mark == mark)
783         {
784             return 1;
785         }
786     }
787     return 0;
788 }
789 
atMacroOpen()790 int History::atMacroOpen()
791 {
792     return atMark(MARK_MACRO_OPEN);
793 }
794 
atMacroClose()795 int History::atMacroClose()
796 {
797     return atMark(MARK_MACRO_CLOSE);
798 }
799 
atScrambleBoundary()800 int History::atScrambleBoundary()
801 {
802     return atMark(MARK_SCRAMBLE_BOUNDARY);
803 }
804 
805 // Local Variables:
806 // c-basic-offset: 4
807 // c-comment-only-line-offset: 0
808 // c-file-offsets: ((defun-block-intro . +) (block-open . 0) (substatement-open . 0) (statement-cont . +) (statement-case-open . +4) (arglist-intro . +) (arglist-close . +) (inline-open . 0))
809 // indent-tabs-mode: nil
810 // End:
811