Report error conditions instead of just doing nothing.
[tagua/yd.git] / src / game.cpp
blob57408018046217cf4b4f2a5acb71668740d6a4a4
1 /*
2 Copyright (c) 2006 Paolo Capriotti <p.capriotti@gmail.com>
3 (c) 2006 Maurizio Monge <maurizio.monge@kdemail.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 */
11 #include <map>
12 #include <KDebug>
13 #ifdef Q_CC_MSVC
14 #pragma warning( push )
15 #pragma warning( disable : 4100 )
16 #include <boost/variant.hpp>
17 #pragma warning( pop )
18 #else
19 #include <boost/variant.hpp>
20 #endif
22 #include <core/moveserializer.h>
23 #include <core/state.h>
24 #include <core/statefactory.h>
25 #include <core/validator.h>
26 #include "variant.h"
28 #include "game.h"
29 #include "game_p.h"
30 #include "pgnparser.h"
31 #include "components.h"
32 #include "variants.h"
34 using namespace GamePrivate;
37 Game::Game(Components* components)
38 : current(-1)
39 , undo_pos(0)
40 , m_components(components) { }
42 Game::~Game() {
45 void Game::onAdded(const Index&) {
48 void Game::onRemoved(const Index&) {
51 void Game::onEntryChanged(const Index&, int) {
54 void Game::onPromoteVariation(const Index&, int) {
57 void Game::onSetComment(const Index&, const QString&) {
60 void Game::onSetVComment(const Index&, int, const QString&) {
63 void Game::onCurrentIndexChanged(const Index&) {
66 void Game::onAvailableUndo(bool) {
69 void Game::onAvailableRedo(bool) {
72 Entry* Game::fetch(const Index& ix) {
73 int at;
74 History *vec = fetchRef(ix, &at);
75 return vec ? &(*vec)[at] : NULL;
78 const Entry* Game::fetch(const Index& ix) const {
79 int at;
80 const History *vec = fetchRef(ix, &at);
81 return vec ? &(*vec)[at] : NULL;
84 History* Game::fetchRef(const Index& ix, int* idx) {
85 if(ix.num_moves >= (int)history.size() || ix.num_moves < 0 )
86 return NULL;
88 History* aretv = &history;
89 Entry* retv = &history[ix.num_moves];
90 if(idx) *idx = ix.num_moves;
92 for(int i=0; i<(int)ix.nested.size();i++) {
93 Variations::iterator it = retv->variations.find(ix.nested[i].variation);
94 if(it == retv->variations.end() || ix.nested[i].num_moves >= (int)it->second.size()
95 || ix.nested[i].num_moves < 0 )
96 return NULL;
98 aretv = &it->second;
99 retv = &it->second[ix.nested[i].num_moves];
100 if(idx) *idx = ix.nested[i].num_moves;
102 return aretv;
105 const History* Game::fetchRef(const Index& ix, int* idx) const {
106 return const_cast<const History*>(const_cast<Game*>(this)->fetchRef(ix, idx));
109 void Game::testMove(const Index& ix) {
110 if (ix != Index(0)) {
111 Entry *e1 = fetch(ix.prev());
112 Entry *e2 = fetch(ix);
113 if (!e1 || !e2 || !e1->position || e2->move == Move())
114 return;
116 if (!m_components->validator()->legal(e1->position.get(), e2->move))
117 kError() << "invalid move added to game history";
121 void Game::testMove() {
122 testMove(current);
125 void Game::saveUndo(const UndoOp& op) {
126 bool redo = undo_pos < (int)undo_history.size();
128 while(undo_pos < (int)undo_history.size())
129 undo_history.pop_back();
130 undo_history.push_back(op);
131 undo_pos++;
133 if(undo_pos == 1)
134 onAvailableUndo(true);
135 if(redo)
136 onAvailableRedo(false);
140 Index Game::index() const {
141 return current;
144 Index Game::lastMainlineIndex() const {
145 return Index(history.size()-1);
148 bool Game::containsIndex(const Index& index) const {
149 return !!fetch(index);
152 Move Game::move() const {
153 return move(current);
156 Move Game::move(const Index& index) const {
157 Entry *e = (Entry*)fetch(index);
158 if(!e) {
159 kError() << "Index out of range";
160 return Move();
162 return e->move;
165 StatePtr Game::position() const {
166 return position(current);
169 StatePtr Game::position(const Index& index) const {
170 Entry *e = (Entry*)fetch(index);
171 if(!e) {
172 kError() << "Index" << index << "out of range";
173 return StatePtr();
175 return e->position;
178 QString Game::comment() const {
179 return comment(current);
182 QString Game::comment(const Index& index) const {
183 const Entry *e = fetch(index);
184 if(!e) {
185 kError() << "Index out of range";
186 return QString();
188 return e->comment;
191 void Game::reset(StatePtr pos) {
192 Q_ASSERT(pos);
194 undo_pos = 0;
195 undo_history.clear();
196 history.clear();
197 history.push_back( Entry(Move(), pos) );
198 current = Index(0);
199 onCurrentIndexChanged();
202 void Game::undo() {
203 if(undo_pos <= 0) {
204 kError() << "Nothing to undo";
205 return;
208 bool last_undo = undo_pos == 1;
209 bool now_redo = undo_pos == (int)undo_history.size();
211 undo_pos--;
212 UndoOp* op = &(undo_history[undo_pos]);
214 if(boost::get<UndoAdd>(op)) {
215 UndoAdd *a = boost::get<UndoAdd>(op);
217 if(a->index.atVariationStart() ) {
218 Entry* e = fetch(a->index.prev());
219 Q_ASSERT(e);
221 int v = a->index.nested.back().variation;
222 Q_ASSERT(e->variations.count(v) == 1);
223 Q_ASSERT(e->variations[v].size() == 1);
225 e->variations.erase(v);
227 else {
228 int at;
229 std::vector<Entry>* vec = fetchRef(a->index, &at);
230 Q_ASSERT(vec);
231 Q_ASSERT((int)vec->size() == at+1);
233 vec->pop_back();
236 if(current == a->index) {
237 current = current.prev();
238 onCurrentIndexChanged();
241 onRemoved(a->index);
243 else if(boost::get<UndoPromote>(op)) {
244 UndoPromote *p = boost::get<UndoPromote>(op);
246 int at;
247 std::vector<Entry>* vec = fetchRef(p->index, &at);
248 Q_ASSERT(vec);
249 Q_ASSERT((*vec)[at].variations.count(p->variation)==1);
251 History vold = (*vec)[at].variations[p->variation];
252 History vnew;
253 for(int i=at+1; i<(int)vec->size(); i++)
254 vnew.push_back((*vec)[i]);
255 while((int)vec->size()>at+1)
256 vec->pop_back();
257 for(int i=0; i<(int)vold.size(); i++)
258 vec->push_back(vold[i]);
259 (*vec)[at].variations[p->variation] = vnew;
261 current = current.flipVariation(p->index, p->variation);
262 onPromoteVariation(p->index, p->variation);
263 //onCurrentIndexChanged();
265 else if(boost::get<UndoTruncate>(op)) {
266 UndoTruncate *t = boost::get<UndoTruncate>(op);
268 int at;
269 std::vector<Entry>* vec = fetchRef(t->index, &at);
270 Q_ASSERT(vec);
271 Q_ASSERT((int)vec->size() == at+1);
272 Q_ASSERT((*vec)[at].variations.empty());
274 for(int i=0;i<(int)t->history.size();i++)
275 vec->push_back(t->history[i]);
276 (*vec)[at].variations = t->variations;
277 (*vec)[at].vcomments = t->vcomments;
279 if(t->history.size())
280 onAdded(t->index.next());
281 for(Variations::iterator it = t->variations.begin(); it != t->variations.end(); ++it)
282 onAdded(t->index.next(it->first));
283 for(VComments::iterator it = t->vcomments.begin(); it != t->vcomments.end(); ++it)
284 onSetVComment(t->index, it->first, it->second);
286 else if(boost::get<UndoRemove>(op)) {
287 UndoRemove *r = boost::get<UndoRemove>(op);
289 Entry *e = fetch(r->index);
290 e->variations[r->variation] = r->history;
291 onAdded(r->index.next(r->variation));
292 if(!r->vcomment.isEmpty()) {
293 e->vcomments[r->variation] = r->vcomment;
294 onSetVComment(r->index, r->variation, r->vcomment);
297 else if(boost::get<UndoClear>(op)) {
298 UndoClear *c = boost::get<UndoClear>(op);
300 Entry *e = fetch(c->index);
301 e->variations = c->variations;
302 e->vcomments = c->vcomments;
303 for(Variations::iterator it = c->variations.begin(); it != c->variations.end(); ++it)
304 onAdded(c->index.next(it->first));
305 for(VComments::iterator it = c->vcomments.begin(); it != c->vcomments.end(); ++it)
306 onSetVComment(c->index, it->first, it->second);
308 else if(boost::get<UndoSetComment>(op)) {
309 UndoSetComment *sc = boost::get<UndoSetComment>(op);
310 Entry *e = fetch(sc->index);
311 Q_ASSERT(e);
313 if(sc->variation == -1) {
314 e->comment = sc->old_comment;
315 onSetComment(sc->index, sc->old_comment);
317 else {
318 if(sc->old_comment.isEmpty())
319 e->vcomments.erase(sc->variation);
320 else
321 e->vcomments[sc->variation] = sc->old_comment;
322 onSetVComment(sc->index, sc->variation, sc->old_comment);
325 else
326 kError() << "Unexpected type in boost::variant";
328 if(last_undo)
329 onAvailableUndo(false);
330 if(now_redo)
331 onAvailableRedo(true);
334 void Game::redo() {
335 if(undo_pos >= (int)undo_history.size()) {
336 kError() << "Nothing to redo";
337 return;
340 bool now_undo = undo_pos == 0;
341 bool last_redo = undo_pos == (int)undo_history.size()-1;
343 UndoOp* op = &(undo_history[undo_pos]);
344 undo_pos++;
346 if(boost::get<UndoAdd>(op)) {
347 UndoAdd *a = boost::get<UndoAdd>(op);
349 if(a->index.atVariationStart() ) {
350 Entry* e = fetch(a->index.prev());
351 Q_ASSERT(e);
353 int v = a->index.nested.back().variation;
354 Q_ASSERT(e->variations.count(v) == 0);
356 History h;
357 h.push_back(a->entry);
358 e->variations[v] = h;
360 else {
361 int at;
362 std::vector<Entry>* vec = fetchRef(a->index.prev(), &at);
363 Q_ASSERT(vec);
364 Q_ASSERT((int)vec->size() == at+1);
366 vec->push_back(a->entry);
369 onAdded(a->index);
370 current = a->index;
371 onCurrentIndexChanged();
373 else if(boost::get<UndoPromote>(op)) {
374 UndoPromote *p = boost::get<UndoPromote>(op);
376 int at;
377 std::vector<Entry>* vec = fetchRef(p->index, &at);
379 Q_ASSERT(vec);
380 Q_ASSERT((*vec)[at].variations.count(p->variation)==1);
381 History vold = (*vec)[at].variations[p->variation];
382 History vnew;
383 for(int i=at+1; i<(int)vec->size(); i++)
384 vnew.push_back((*vec)[i]);
385 while((int)vec->size()>at+1)
386 vec->pop_back();
387 for(int i=0; i<(int)vold.size(); i++)
388 vec->push_back(vold[i]);
389 (*vec)[at].variations[p->variation] = vnew;
391 current = current.flipVariation(p->index, p->variation);
392 onPromoteVariation(p->index, p->variation);
393 //onCurrentIndexChanged();
395 else if(boost::get<UndoTruncate>(op)) {
396 UndoTruncate *t = boost::get<UndoTruncate>(op);
398 int at;
399 std::vector<Entry>* vec = fetchRef(t->index, &at);
400 Q_ASSERT(vec);
401 Q_ASSERT((int)vec->size() == at+1+(int)t->history.size());
403 while((int)vec->size() > at+1)
404 vec->pop_back();
405 (*vec)[at].variations.clear();
406 (*vec)[at].vcomments.clear();
408 if(current > t->index) {
409 current = t->index;
410 onCurrentIndexChanged();
413 if(t->history.size())
414 onRemoved(t->index.next());
415 for(Variations::iterator it = t->variations.begin(); it != t->variations.end(); ++it)
416 onRemoved(t->index.next(it->first));
418 else if(boost::get<UndoRemove>(op)) {
419 UndoRemove *r = boost::get<UndoRemove>(op);
421 Entry *e = fetch(r->index);
422 e->variations.erase(r->variation);
423 e->vcomments.erase(r->variation);
424 onRemoved(r->index.next(r->variation));
426 else if(boost::get<UndoClear>(op)) {
427 UndoClear *c = boost::get<UndoClear>(op);
429 Entry *e = fetch(c->index);
430 e->variations.clear();
431 e->vcomments.clear();
432 for(Variations::iterator it = c->variations.begin(); it != c->variations.end(); ++it)
433 onRemoved(c->index.next(it->first));
435 else if(boost::get<UndoSetComment>(op)) {
436 UndoSetComment *sc = boost::get<UndoSetComment>(op);
437 Entry *e = fetch(sc->index);
438 Q_ASSERT(e);
440 if(sc->variation == -1) {
441 e->comment = sc->new_comment;
442 onSetComment(sc->index, sc->new_comment);
444 else {
445 if(sc->new_comment.isEmpty())
446 e->vcomments.erase(sc->variation);
447 else
448 e->vcomments[sc->variation] = sc->new_comment;
449 onSetVComment(sc->index, sc->variation, sc->new_comment);
452 else
453 kError() << "Unexpected type in boost::variant";
455 if(now_undo)
456 onAvailableUndo(true);
457 if(last_redo)
458 onAvailableRedo(false);
461 void Game::setComment(const QString& c) {
462 setComment(current, c);
465 void Game::setComment(const Index& ix, const QString& c) {
466 Entry* e = fetch(ix);
467 if(!e) {
468 kError() << "Invalid index";
469 return;
471 if(e->comment == c)
472 return;
474 saveUndo(UndoSetComment(ix, -1, e->comment, c));
475 e->comment = c;
476 onSetComment(ix, c);
479 void Game::setVComment(const Index& ix, int v, const QString& c) {
480 Entry* e = fetch(ix);
481 if(!e) {
482 kError() << "Invalid index";
483 return;
485 QString oc = e->vcomments.count(v) ? e->vcomments[v] : QString();
486 if(oc == c)
487 return;
489 saveUndo(UndoSetComment(ix, v, oc, c));
490 if(c.isEmpty())
491 e->vcomments.erase(v);
492 else
493 e->vcomments[v] = c;
494 onSetVComment(ix, v, c);
497 void Game::promoteVariation() {
498 promoteVariation(current);
501 void Game::promoteVariation(const Index& _ix) {
502 if(_ix.nested.size()==0) {
503 kError() << "Cannot promote main line";
504 return;
506 Index ix = _ix;
507 int v = ix.nested.back().variation;
508 ix.nested.pop_back();
510 promoteVariation(ix, v);
513 void Game::promoteVariation(const Index& ix, int v) {
514 int at;
515 std::vector<Entry>* vec = fetchRef(ix, &at);
516 Q_ASSERT(vec);
517 Q_ASSERT((*vec)[at].variations.count(v)==1);
519 History vold = (*vec)[at].variations[v];
520 History vnew;
521 for(int i=at+1; i<(int)vec->size(); i++)
522 vnew.push_back((*vec)[i]);
523 while((int)vec->size()>at+1)
524 vec->pop_back();
525 for(int i=0; i<(int)vold.size(); i++)
526 vec->push_back(vold[i]);
527 (*vec)[at].variations[v] = vnew;
529 saveUndo(UndoPromote(ix, v));
530 current = current.flipVariation(ix, v);
531 onPromoteVariation(ix, v);
532 //don't call onCurrentIndexChanged(), as the position did not change actually
535 void Game::removeVariation(int v) {
536 removeVariation(current, v);
539 void Game::removeVariation(const Index& _ix) {
540 if(_ix.nested.size()==0) {
541 kError() << "Cannot remove main line";
542 return;
544 Index ix = _ix;
545 int v = ix.nested.back().variation;
546 ix.nested.pop_back();
548 removeVariation(ix, v);
551 void Game::removeVariation(const Index& ix, int v) {
552 Entry* e = fetch(ix);
554 saveUndo(UndoRemove(ix, v, e->variations[v],
555 e->vcomments.count(v) ? e->vcomments[v] : QString() ));
556 e->variations.erase(v);
557 e->vcomments.erase(v);
559 onRemoved(ix.next(v));
560 if(current >= ix.next(v)) {
561 current = ix;
562 onCurrentIndexChanged();
566 void Game::clearVariations() {
567 clearVariations(current);
570 void Game::clearVariations(const Index& ix) {
571 Entry* e = fetch(ix);
573 UndoClear uc(ix, e->variations, e->vcomments);
574 saveUndo(uc);
575 e->variations.clear();
576 e->vcomments.clear();
578 for(Variations::iterator it = uc.variations.begin(); it != uc.variations.end(); ++it)
579 onRemoved(ix.next(it->first));
580 if(current > ix && !(current >= ix.next())) {
581 current = ix;
582 onCurrentIndexChanged();
586 void Game::truncate() {
587 truncate(current);
590 void Game::truncate(const Index& ix) {
591 int at;
592 History* vec = fetchRef(ix, &at);
593 if(!vec) {
594 kError() << "Truncating at an unexisting index";
595 return;
598 Entry *e = &(*vec)[at];
599 UndoTruncate undo(ix);
600 for(int i=at+1; i<(int)vec->size();i++)
601 undo.history.push_back((*vec)[i]);
602 while((int)vec->size()>at+1)
603 vec->pop_back();
605 undo.variations = e->variations;
606 undo.vcomments = e->vcomments;
607 saveUndo(undo);
608 e->variations.clear();
609 e->vcomments.clear();
611 if(undo.history.size())
612 onRemoved(undo.index.next());
613 for(Variations::iterator it = undo.variations.begin(); it != undo.variations.end(); ++it)
614 onRemoved(undo.index.next(it->first));
616 if(current > ix) {
617 current = ix;
618 onCurrentIndexChanged();
622 void Game::add(const Move& m, const StatePtr& pos) {
623 Q_ASSERT(pos);
625 Index old_c = current;
626 int at;
627 std::vector<Entry>* vec = fetchRef(current, &at);
628 Q_ASSERT(vec);
630 /* add the move on the mainline */
631 if((int)vec->size() <= at+1 ) {
632 Q_ASSERT((int)vec->size() == at+1);
633 vec->push_back(Entry(m, pos));
634 current = current.next();
635 testMove();
636 saveUndo(UndoAdd(current, Entry(m, pos)));
637 onAdded(current);
638 onCurrentIndexChanged(old_c);
640 /* we are playing the move that is already next in the mainline */
641 else if( (*vec)[at+1].position && (*vec)[at+1].position->equals(pos.get())) {
642 current = current.next();
643 onCurrentIndexChanged(old_c);
644 /* no need to test the move */
646 else {
647 Entry *e = fetch(current);
648 Q_ASSERT(e);
650 /* check if a variations with this move already exists. */
651 for(Variations::iterator it = e->variations.begin(); it != e->variations.end(); ++it)
652 if(it->second.size() > 0 && it->second[0].position
653 && it->second[0].position->equals(pos.get()) ) {
654 current = current.next(it->first);
655 onCurrentIndexChanged(old_c);
657 return;
660 int var_id = e->last_var_id++;
661 e->variations[var_id].push_back(Entry(m, pos));
662 current = current.next(var_id);
663 testMove();
664 saveUndo(UndoAdd(current, Entry(m, pos)));
665 onAdded(current);
666 onCurrentIndexChanged(old_c);
670 bool Game::insert(const Move& m, const StatePtr& pos, const Index& at) {
671 Entry *e = fetch(at);
673 if(!e) {
674 if(at.nested.size() == 0) {
675 if(undo_history.size()) {
676 undo_pos = 0;
677 undo_history.clear();
679 int hs = history.size();
680 history.resize(at.num_moves + 1);
681 history[at.num_moves] = Entry(m, pos);
682 testMove(at);
683 onAdded(Index(hs));
684 return true;
686 else {
687 kError() << "Index out if range";
688 return false;
692 if(undo_history.size()) {
693 undo_pos = 0;
694 undo_history.clear();
696 bool res = e->position && e->position->equals(pos.get());
697 e->move = m;
698 e->position = pos;
699 testMove(at);
700 testMove(at.next());
701 for (Variations::const_iterator it = e->variations.begin();
702 it != e->variations.end(); ++it)
703 testMove(at.next(it->first));
704 onEntryChanged(at);
705 return res;
708 bool Game::lastPosition() const {
709 return !fetch(current.next());
712 bool Game::back() {
713 if (current <= 0) return false; // first entry or uninitialized
714 Index old_c = current;
715 Index new_c = current.prev();
717 Entry *e = fetch(new_c);
718 if(!e || e->position == 0) return false; // gap immediately before current
719 current = new_c;
720 onCurrentIndexChanged(old_c);
722 return true;
725 bool Game::forward() {
726 Index old_c = current;
727 Index new_c = current.next();
729 Entry *e = fetch(new_c);
730 if(!e || e->position == 0) {
731 return false; // gap immediately before current
733 current = new_c;
734 onCurrentIndexChanged(old_c);
736 return true;
739 void Game::gotoFirst() {
740 Index old_c = current;
741 current = Index(0);
742 onCurrentIndexChanged(old_c);
745 void Game::gotoLast() {
746 int at;
747 std::vector<Entry>* vec = fetchRef(current, &at);
748 Q_ASSERT(vec);
749 Q_ASSERT((int)vec->size() > at);
751 if((int)vec->size() > at+1) {
752 Index old_c = current;
753 current = current.next(-1, vec->size()-1-at);
754 onCurrentIndexChanged(old_c);
758 bool Game::goTo(const Index& index) {
759 if (fetch(index)) {
760 Index old_c = current;
761 current = index;
762 onCurrentIndexChanged(old_c);
763 return true;
765 return false;
768 QString Game::variationPgn(const History& vec, const Entry& e,
769 int start, const Index& _ix) const {
770 Index ix = _ix;
771 QString res;
773 for (int i = start; i < static_cast<int>(vec.size()); i++) {
774 const Entry& preve = (i > start) ? vec[i-1] : e;
777 QString mv = (vec[i].move != Move() && preve.position)
778 ? m_components->moveSerializer("compact")->
779 serialize(vec[i].move, preve.position.get())
780 : "???";
781 #if 0
782 if (ix == current)
783 mv = "[[" + mv + "]]";
784 #endif
786 int n = ix.totalNumMoves()+1;
787 if(i==start || n%2==0)
788 mv = QString::number(n/2)+(n%2==1 ? ". ... " : ". ") + mv;
789 if (i > start)
790 mv = " " + mv;
792 res += mv;
794 if(!vec[i].comment.isEmpty())
795 res += " {" + vec[i].comment + "}";
797 if(i > 0) {
798 for(Variations::const_iterator it = vec[i-1].variations.begin();
799 it != vec[i-1].variations.end(); ++it) {
800 res += " (";
801 if(vec[i-1].vcomments.count(it->first))
802 res += "{" + vec[i-1].vcomments.find(it->first)->second + "} ";
803 res += variationPgn(it->second, vec[i - 1], 0,
804 ix.prev().next(it->first)) + ")";
808 ix = ix.next();
810 return res;
813 QString Game::pgn() const {
814 return variationPgn(history, history[0], 1, Index(1));
817 void Game::load(const PGN& pgn) {
818 std::map<QString, QString>::const_iterator var = pgn.m_tags.find("Variant");
819 Variant* vi;
821 // FIXME do not create a variant here
822 if (var == pgn.m_tags.end()) {
823 if (!(vi = Variants::self().create("chess"))) {
824 kError() << "No such variant 'chess'";
825 return;
828 else if (!(vi = Variants::self().create(var->second))) {
829 kError() << "No such variant" << var->second;
830 return;
833 std::map<QString, QString>::const_iterator fen = pgn.m_tags.find("FEN");
834 StatePtr pos;
836 //if(var == pgn.m_tags.end()) {
837 pos = StatePtr(m_components->createState());
838 pos->setup();
840 #if 0 // BROKEN
841 else if( !(pos = vi->createPositionFromFEN(fen->second))) {
842 kError() << "Wrong fen " << fen->second;
843 return;
845 #endif
847 //TODO: what about options? FEN rules?
849 load(pos, pgn);
852 void Game::load(StatePtr pos, const PGN& pgn) {
853 current = Index(0);
854 undo_history.clear();
855 undo_pos = 0;
857 // setup an empty history, clear as needed
859 if(history.size()) {
860 Entry* fe = &history[0];
861 int old_history_size = history.size();
862 std::vector<int> v_ids;
864 while(history.size()>1)
865 history.pop_back();
866 for(Variations::const_iterator it = fe->variations.begin();
867 it != fe->variations.end(); ++it)
868 v_ids.push_back(it->first);
869 fe->variations.clear();
870 fe->vcomments.clear();
872 for(int i=0;i<(int)v_ids.size();i++)
873 onRemoved(Index(0).next(v_ids[i]));
874 if(old_history_size>1)
875 onRemoved(Index(1));
876 v_ids.clear();
877 history[0].position = pos;
879 else
880 history.push_back( Entry(Move(), pos) );
882 // apply moves from PGN, one by one
884 QString vcomment;
885 std::vector<Index> var_stack;
886 bool var_start = false;
888 for (uint i = 0; i < pgn.m_entries.size(); i++) {
889 if(boost::get<QString>(pgn[i])) {
890 if(var_start)
891 vcomment += *boost::get<QString>(pgn[i]);
892 else {
893 Entry *e = fetch(current);
894 Q_ASSERT(e);
896 e->comment += *boost::get<QString>(pgn[i]);
899 else if(boost::get<PGN::BeginVariation>(pgn[i])) {
900 var_stack.push_back(current);
901 var_start = true;
903 else if(boost::get<PGN::EndVariation>(pgn[i])) {
904 if(var_stack.size() == 0) {
905 kError() << "Unexpected end variation";
906 break;
908 current = var_stack.back();
909 var_stack.pop_back();
911 else if(boost::get<PGN::Move>(pgn[i])) {
912 const PGN::Move *pm = boost::get<PGN::Move>(pgn[i]);
914 int n = current.totalNumMoves()+1;
915 if(var_start) {
916 if(!pm->m_number) // not all moves get numbered in PGN, usually only 1st player ones
917 current = current.prev();
918 else if(pm->m_number>n+1)
919 kError() << "Too far variation";
920 else {
921 if(pm->m_number<n)
922 kError() << "Too close variation";
923 current = current.prev(n + 1 - pm->m_number);
926 else if(pm->m_number && pm->m_number!=n+1)
927 kError() << "Move number mismatch";
929 StatePtr pos = position();
930 Move m = m_components->moveSerializer("compact")->
931 deserialize(pm->m_move, pos.get());
933 if (m == Move() || !m_components->validator()->legal(pos.get(), m))
934 break;
936 StatePtr newPos(pos->clone());
937 newPos->move(m);
939 int at;
940 History *vec = fetchRef(current, &at);
941 Q_ASSERT(vec);
943 if(var_start) {
944 Entry *e = &(*vec)[at];
945 int var_id = e->last_var_id++;
946 e->variations[var_id].push_back(Entry(m, newPos));
947 if(!vcomment.isEmpty()) {
948 e->vcomments[var_id] = vcomment;
949 vcomment = QString();
951 /* this is a hack, but the mainline should NEVER
952 be empty if there is a variation*/
953 if((int)vec->size() - 1 == at)
954 vec->push_back(Entry(m, newPos));
956 current = current.next(var_id);
958 else {
959 if((int)vec->size() - 1 == at)
960 vec->push_back(Entry(m, newPos));
961 else
962 (*vec)[at] = Entry(m, newPos);
964 current = current.next();
967 var_start = false;
969 else
970 kError() << "Unexpected type in boost::variant";
973 if(history.size()>1)
974 onAdded(Index(1));
975 Entry* e = fetch(Index(0));
976 for(Variations::const_iterator it = e->variations.begin();
977 it != e->variations.end(); ++it)
978 onAdded(Index(0).next(it->first));
979 for(VComments::const_iterator it = e->vcomments.begin();
980 it != e->vcomments.end(); ++it)
981 onSetVComment(Index(0), it->first, it->second);
983 current = Index(0);
984 onCurrentIndexChanged();