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