Rough PGN save support.
[tagua/yd.git] / src / board.cpp
blob793a07197ab3f52620e14c8e4ce12f94c91cc4e8
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 <math.h>
12 #include <iostream>
13 #include <QPainter>
14 #include <QApplication>
15 #include <QMouseEvent>
17 #include "mastersettings.h"
18 #include "board.h"
19 #include "sprite.h"
20 #include "animation.h"
21 #include "pointconverter.h"
22 #include "entities/userentity.h"
23 #include "mainanimation.h"
24 #include "premove.h"
25 #include "constrainedtext.h"
27 using namespace boost;
29 /** inherit instead of typedef to ease forward declaration :) */
30 class BoardTags : public std::map<QString, std::map<Point, boost::shared_ptr<KGameCanvasPixmap> > > {
33 Board::Board(const AnimationSettings& animSettings, KGameCanvasAbstract* parent)
34 : ClickableCanvas(parent)
35 , m_flipped(false)
36 , m_square_size(0)
37 , m_border_size(0)
38 , m_sprites(0,0)
39 , m_hinting_pos(Point::invalid())
40 , selection(Point::invalid())
41 , lastSelection(Point::invalid())
42 , m_dropped_pool(-1)
43 , m_dropped_index(-1)
44 , m_anim_settings(animSettings) {
46 m_main_animation = new MainAnimation( 1.0 );
48 m_tags = BoardTagsPtr(new BoardTags);
50 m_canvas_background = new KGameCanvasGroup(this);
51 m_canvas_background->show();
53 m_canvas_border = new KGameCanvasGroup(this);
54 m_canvas_border->show();
56 m_canvas_border_text = new KGameCanvasGroup(this);
57 m_canvas_border_text->show();
59 m_pieces_group = new KGameCanvasGroup(this);
60 m_pieces_group->show();
62 settingsChanged();
65 Board::~Board() {
66 delete m_pieces_group;
68 while(!m_canvas_background->items()->isEmpty())
69 delete m_canvas_background->items()->first();
70 delete m_canvas_background;
72 while(!m_canvas_border->items()->isEmpty())
73 delete m_canvas_border->items()->first();
74 delete m_canvas_border;
76 delete m_main_animation;
79 void Board::settingsChanged() {
80 Settings s_anim = settings().group("animations");
81 int speed = (s_anim["speed"] | 16);
82 int smoothness = (s_anim["smoothness"] | 16);
83 m_main_animation->setSpeed( 0.4*pow(10.0, speed/32.0) );
84 m_main_animation->setDelay( int(70.0*pow(10.0, -smoothness/32.0)) );
86 m_border_text_color = m_controls_loader.getStaticValue<QColor>("border_color");
87 m_border_font = m_controls_loader.getStaticValue<QFont>("border_font");
89 recreateBorder();
92 void Board::updateBackground() {
93 while(!m_canvas_background->items()->isEmpty())
94 delete m_canvas_background->items()->first();
96 if(!m_square_size)
97 return;
99 Loader::PixmapOrMap bg = m_tags_loader.getValue<Loader::PixmapOrMap>("background");
100 if(const QPixmap* p = boost::get<QPixmap>(&bg)) {
101 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(*p, boardRect().size(), QPoint(),
102 true, m_canvas_background);
103 t->show();
105 else if(const Loader::PixmapMap* p = boost::get<Loader::PixmapMap>(&bg)) {
106 for(Loader::PixmapMap::const_iterator it = p->begin(); it != p->end(); ++it) {
107 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(it->second, it->first.size(),
108 QPoint(), true, m_canvas_background);
109 t->moveTo(it->first.topLeft());
110 t->show();
115 void Board::enqueue(const shared_ptr<Animation>& anim) {
116 m_main_animation->addAnimation(anim);
119 void Board::adjustSprite(const Point& p, bool immediate) {
120 SpritePtr sprite = m_sprites[p].sprite();
122 if(!sprite)
123 return;
125 enqueue(
126 1 /*BROKEN m_anim_movement*/ && !immediate
127 ? AnimationPtr(new MovementAnimation(sprite, converter()->toReal(p), 1.0))
128 : AnimationPtr(new InstantAnimation(sprite, converter()->toReal(p)))
132 boost::shared_ptr<KGameCanvasPixmap> Board::addTag(const QString& name, Point pt, bool over) {
133 if(!m_sprites.valid(pt))
134 return boost::shared_ptr<KGameCanvasPixmap>();
136 QPixmap p = m_tags_loader.getPixmap(name);
137 boost::shared_ptr<KGameCanvasPixmap> item =
138 boost::shared_ptr<KGameCanvasPixmap>(new KGameCanvasPixmap(p, this));
139 item->moveTo(converter()->toReal(pt));
140 if(over)
141 item->stackOver(m_pieces_group);
142 else
143 item->stackUnder(m_pieces_group);
144 item->show();
146 (*m_tags)[name][pt] = item;
147 return item;
150 void Board::clearTags(const QString& name) {
151 m_tags->erase(name);
154 void Board::clearTags() {
155 m_tags->clear();
158 void Board::setTags(const QString& name, Point p1, Point p2, Point p3,
159 Point p4, Point p5, Point p6 ) {
160 //TODO: maybe this could be optimized a bit
161 clearTags(name);
162 addTag(name, p1);
163 addTag(name, p2);
164 addTag(name, p3);
165 addTag(name, p4);
166 addTag(name, p5);
167 addTag(name, p6);
170 void Board::recreateBorder() {
171 m_border_text.clear();
172 while(!m_canvas_border_text->items()->isEmpty())
173 delete m_canvas_border_text->items()->first();
175 if(m_border_coords.size() == 0)
176 return;
178 Point s = m_sprites.getSize();
179 for(int w = 0; w<2; w++)
180 for(int i = 0;i<s.x;i++) {
181 int c = w ? i : i+s.x+s.y;
182 QString l = m_border_coords.size()>c ? m_border_coords[c] : QString();
183 ConstrainedText *item = new ConstrainedText(m_canvas_border_text);
184 item->setColor(m_border_text_color);
185 item->setText(l);
186 item->setFont(m_border_font);
187 item->setColor(m_border_text_color);
188 item->show();
189 m_border_text.push_back(item);
192 for(int w = 0; w<2; w++)
193 for(int i = 0;i<s.y;i++) {
194 int c = w ? i+s.x : i+2*s.x+s.y;
195 QString n = m_border_coords.size()>c ? m_border_coords[c] : QString();
196 ConstrainedText *item = new ConstrainedText(m_canvas_border_text);
197 item->setColor(m_border_text_color);
198 item->setText(n);
199 item->setFont(m_border_font);
200 item->setColor(m_border_text_color);
201 item->show();
202 m_border_text.push_back(item);
205 m_pieces_group->raise();
207 updateBorder();
210 void Board::updateBorder() {
211 while(!m_canvas_border->items()->isEmpty())
212 delete m_canvas_border->items()->first();
214 if(!m_square_size)
215 return;
217 int at = 0;
218 for(int w = 0; w<2; w++)
219 for(int i = 0;i<m_sprites.getSize().x;i++) {
220 int x = (m_flipped ? (m_sprites.getSize().x-1-i) : i)*m_square_size;
221 int y = w ? -m_border_text_far : m_square_size*m_sprites.getSize().y+m_border_text_near;
223 m_border_text[at]->setVisible(m_border_text_near != m_border_text_far);
224 m_border_text[at]->setConstrainRect(QRect(x,y,m_square_size,m_border_text_far-m_border_text_near));
225 at++;
228 for(int w = 0; w<2; w++)
229 for(int i = 0;i<m_sprites.getSize().y;i++) {
230 int x = w ? (-m_border_text_far-m_border_text_near)/2
231 : m_square_size*m_sprites.getSize().x + (m_border_text_far+m_border_text_near)/2;
232 int y = (!m_flipped ? (m_sprites.getSize().y-1-i) : i)*m_square_size
233 + (m_square_size-m_border_text_far-m_border_text_near)/2;
235 m_border_text[at]->setVisible(m_border_text_near != m_border_text_far);
236 m_border_text[at]->setConstrainRect(QRect(x-m_square_size/2,y,m_square_size,m_border_text_far-m_border_text_near));
237 at++;
240 ::LuaApi::LuaValueMap params;
241 params["width"] = m_square_size*m_sprites.getSize().x;
242 params["height"] = m_square_size*m_sprites.getSize().y;
243 Loader::PixmapOrMap bord = m_controls_loader.getValue<Loader::PixmapOrMap>("border", &params);
244 if(const QPixmap* p = boost::get<QPixmap>(&bord)) {
245 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(*p, boardRect().size(), QPoint(),
246 true, m_canvas_border);
247 t->show();
249 else if(const Loader::PixmapMap* p = boost::get<Loader::PixmapMap>(&bord)) {
250 for(Loader::PixmapMap::const_iterator it = p->begin(); it != p->end(); ++it) {
251 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(it->second, it->first.size(),
252 QPoint(), true, m_canvas_border);
253 t->moveTo(it->first.topLeft());
254 t->show();
259 void Board::createGrid(Point p, const QStringList& border_coords) {
260 m_border_coords = border_coords;
261 m_sprites = PieceGrid(p.x,p.y);
262 recreateBorder();
265 QPixmap Board::loadSprite(const QString& id) {
266 return m_loader.piecePixmap(id, m_flipped);
269 QRect Board::computeRect(Point p) const {
270 QPoint realPoint = converter()->toReal(p);
271 return squareRect(realPoint.x(), realPoint.y());
274 QRect Board::squareRect(int x, int y) const {
275 return QRect(x, y, m_square_size, m_square_size);
278 QRegion Board::computeRegion(Point p) const {
279 return QRegion(computeRect(p));
282 // selection
283 void Board::setSelection(const Point& p) {
284 lastSelection = selection;
285 selection = p;
286 setTags("selection", p);
289 void Board::cancelSelection() {
290 lastSelection = selection;
291 selection = Point::invalid();
292 clearTags("selection");
295 // premove
297 void Board::setPremove(const NormalUserMove& premove) {
298 m_premove_from = premove.from;
299 m_premove_to = premove.to;
300 setTags("premove", m_premove_from, m_premove_to);
303 void Board::setPremove(const DropUserMove& premove) {
304 m_premove_from = Point::invalid();
305 m_premove_to = premove.to;
306 setTags("premove", m_premove_to);
309 void Board::setPremove(const Premove& premove) {
310 setPremove(premove.toUserMove());
313 void Board::cancelPremove() {
314 m_premove_from = Point::invalid();
315 m_premove_to = Point::invalid();
316 clearTags("premove");
319 void Board::updateSprites() {
320 // adjust piece positions
321 for (Point i = m_sprites.first(); i <= m_sprites.last(); i = m_sprites.next(i)) {
322 boost::shared_ptr<Sprite> p = m_sprites[i].sprite();
324 if (p) {
325 // drawing sprite
326 p->setPixmap(loadSprite(m_sprites[i].name()));
327 adjustSprite(i, true);
332 void Board::updateTags() {
333 if(!m_square_size)
334 return;
336 for(BoardTags::iterator tit = m_tags->begin(); tit != m_tags->end(); ++tit)
337 for(std::map<Point, boost::shared_ptr<KGameCanvasPixmap> >::iterator pt =
338 tit->second.begin(); pt != tit->second.end(); ++pt) {
339 pt->second->moveTo(converter()->toReal(pt->first));
340 pt->second->setPixmap(m_tags_loader.getPixmap(tit->first));
345 bool Board::doMove(const NormalUserMove& m) {
346 if (m_entity.lock()->oneClickMoves() || m_entity.lock()->validTurn(m.from) == Moving) {
347 AbstractMove::Ptr mv = m_entity.lock()->testMove(m);
348 if (mv) {
349 m_entity.lock()->executeMove(mv);
350 return true;
354 std::cout << "invalid move" << std::endl;
355 error(InvalidMove);
357 return false;
361 void Board::onResize(int new_size, int border_size, int border_text_near,
362 int border_text_far, bool force_reload) {
363 if(m_square_size == new_size && !force_reload)
364 return;
366 m_square_size = new_size;
367 m_border_size = border_size;
368 m_border_text_near = border_text_near;
369 m_border_text_far = border_text_far;
371 // update the size of the piece loader
372 m_loader.setSize(m_square_size);
374 // update the size of the tag loader
375 m_tags_loader.setSize(m_square_size);
377 // update the size of the controls loader
378 m_controls_loader.setSize(m_border_size);
380 // update canvas background
381 updateBackground();
383 // update the sprites
384 updateSprites();
386 // update piece tags
387 updateTags();
389 // update border
390 updateBorder();
393 void Board::onMousePress(const QPoint& pos, int button) {
394 Point point = converter()->toLogical(pos);
395 if (!m_sprites.valid(point))
396 return;
398 //BEGIN left click
400 if (button == Qt::LeftButton) {
401 if (m_entity.lock()->oneClickMoves()) {
402 NormalUserMove m = m_entity.lock()->createMove(Point::invalid(), point);
403 doMove(m);
405 else {
406 shared_ptr<Sprite> piece = m_sprites[point].sprite();
408 if (piece && m_entity.lock()->movable(point)) {
409 cancelSelection();
410 m_drag_info = DragInfoPtr(new DragInfo(point, pos, piece,
411 m_entity.lock()->validTurn(point)) );
412 piece->raise();
415 // if selection is valid, (pre)move to point
416 else if (selection != Point::invalid()) {
417 piece = m_sprites[selection].sprite();
418 Q_ASSERT(piece);
419 NormalUserMove m = m_entity.lock()->createMove(selection, point);
421 switch(m_entity.lock()->validTurn(selection)) {
423 case Moving:
424 doMove(m);
425 cancelSelection();
426 break;
428 case Premoving:
429 if (m_entity.lock()->testPremove(m)) {
430 m_entity.lock()->addPremove(m);
431 setPremove(m);
432 cancelSelection();
434 break;
436 default:
437 break;
443 //END left click
445 //BEGIN right click
447 else if (button == Qt::RightButton) {
448 cancelSelection();
449 if (point == m_premove_from || point == m_premove_to)
450 cancelPremove();
451 m_entity.lock()->handleRightClick(point);
454 //END right click
457 void Board::onMouseRelease(const QPoint& pos, int button) {
458 Point point = converter()->toLogical(pos);
460 //BEGIN left click
462 if (button == Qt::LeftButton) {
464 if (m_drag_info) {
465 // Q_ASSERT(m_drag_info->piece);
466 Q_ASSERT(m_drag_info->sprite);
467 bool moved = false;
469 // remove valid move highlighting
470 clearTags("validmove");
472 // toggle selection if the piece didn't move
473 if (m_drag_info->from == point) {
474 if (lastSelection == point)
475 cancelSelection();
476 else
477 setSelection(point);
480 else {
481 NormalUserMove m = m_entity.lock()->createMove(m_drag_info->from, point);
482 if (!m_sprites.valid(point))
483 m.to = Point::invalid();
485 switch(m_entity.lock()->validTurn(m_drag_info->from)) {
487 case Moving:
488 if (doMove(m))
489 moved = true;
490 break;
492 case Premoving:
493 if (m_entity.lock()->testPremove(m)) {
494 m_entity.lock()->addPremove(m);
495 setPremove(m);
497 break;
499 default:
500 break;
504 shared_ptr<Sprite> s = m_sprites[m_drag_info->from].sprite();
505 if (!moved && s && s->pos() != converter()->toReal(m_drag_info->from)) {
506 Q_ASSERT(s);
507 QPoint real = converter()->toReal(m_drag_info->from);
508 if( (point == m_drag_info->from) ? 0/* !m_anim_movement*/ : 0 /* !m_anim_fade*/) //BROKEN
509 enqueue(shared_ptr<Animation>(new InstantAnimation(s, real)));
510 else if (point == m_drag_info->from)
511 enqueue(shared_ptr<Animation>(new MovementAnimation(s, real)));
512 else
513 enqueue(shared_ptr<Animation>(new TeleportAnimation(s, s->pos(), real)));
516 m_drag_info = DragInfoPtr();
520 //END left button
523 void Board::onMouseMove(const QPoint& pos, int /*button*/) {
524 Point point = converter()->toLogical(pos);
526 if (m_drag_info) {
527 Q_ASSERT(m_drag_info->sprite);
528 // check drag threshold
529 if (!m_drag_info->dragStarted) {
530 QPoint delta = pos - m_drag_info->real;
531 if (delta.x() * delta.x() + delta.y() * delta.y() > DragInfo::DRAG_THRESHOLD) {
532 m_drag_info->dragStarted = true;
535 if (m_drag_info->dragStarted)
536 m_drag_info->sprite->moveTo(pos - QPoint(m_square_size / 2, m_square_size / 2) );
538 // highlight valid moves
539 NormalUserMove move = m_entity.lock()->createMove(m_drag_info->from, point);
540 bool valid = m_sprites.valid(point);
541 if (valid) {
542 InteractionType action = m_entity.lock()->validTurn(m_drag_info->from);
543 if (action == Moving)
544 valid = m_entity.lock()->testMove(move);
547 if (valid)
548 setTags("validmove", point);
549 else
550 clearTags("validmove");
552 else if (m_entity.lock()->oneClickMoves()) {
553 if(point == m_hinting_pos)
554 return;
556 AbstractPiece::Ptr hint;
558 if (m_sprites.valid(point)) {
559 if (AbstractMove::Ptr move = m_entity.lock()->testMove(
560 m_entity.lock()->createMove(Point::invalid(), point))) {
561 // set move hint
562 hint = m_entity.lock()->moveHint(move);
566 updateHinting(point, hint);
570 void Board::onPositionChanged() {
571 if (m_entity.lock() && m_entity.lock()->oneClickMoves() && m_sprites.valid(m_hinting_pos)) {
572 AbstractPiece::Ptr hint;
574 if (AbstractMove::Ptr move = m_entity.lock()->testMove(
575 m_entity.lock()->createMove(Point::invalid(), m_hinting_pos))) {
576 // set move hint
577 hint = m_entity.lock()->moveHint(move);
580 updateHinting(m_hinting_pos, hint);
584 void Board::onMouseLeave() {
585 updateHinting(Point::invalid(), AbstractPiece::Ptr());
588 void Board::updateHinting(Point pt, AbstractPiece::Ptr piece) {
589 if(!m_sprites.valid(pt))
590 piece = AbstractPiece::Ptr();
592 if(!piece || !m_sprites.valid(pt)) {
593 if(m_hinting.sprite()) {
594 if(1 /*BROKEN m_anim_fade*/)
595 enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
596 m_hinting.sprite()->pos(), 160, 0)) );
597 else
598 enqueue( boost::shared_ptr<Animation>(new CaptureAnimation(m_hinting.sprite())) );
601 m_hinting_pos = Point::invalid();
602 m_hinting = NamedSprite();
604 else {
605 if(pt == m_hinting_pos) {
606 if(!(piece->name() == m_hinting.name())) {
607 m_hinting = NamedSprite(piece->name(), m_hinting.sprite());
608 m_hinting.sprite()->setPixmap(loadSprite(piece->name()));
611 else {
612 if(m_hinting.sprite()) {
613 if(1 /*BROKEN m_anim_fade*/)
614 enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
615 m_hinting.sprite()->pos(), 160, 0)) );
616 else
617 enqueue( boost::shared_ptr<Animation>(new CaptureAnimation(m_hinting.sprite())) );
620 QPixmap pix = loadSprite(piece->name());
621 SpritePtr sprite(new Sprite(pix, piecesGroup(), converter()->toReal(pt)));
622 sprite->setOpacity(160);
623 sprite->raise();
624 sprite->show();
626 m_hinting_pos = pt;
627 m_hinting = NamedSprite(piece->name(), sprite);
629 /*if(m_anim_fade)
630 enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
631 m_hinting.sprite()->pos(), 0, 160)) );
632 else {
633 m_hinting.sprite()->setOpacity(160);
634 enqueue(boost::shared_ptr<Animation>(new DropAnimation(m_hinting.sprite())) );
640 void Board::reset() {
641 clearTags();
642 cancelSelection();
643 cancelPremove();
644 m_main_animation->stop();
647 void Board::flip(bool flipped)
649 if (m_flipped != flipped) {
650 m_flipped = flipped;
652 // update sprite positions
653 for (Point i = m_sprites.first(); i <= m_sprites.last(); i = m_sprites.next(i)) {
654 SpritePtr p = m_sprites[i].sprite();
655 if (p) {
656 p->setPixmap(loadSprite(m_sprites[i].name()));
657 adjustSprite(i, true);
661 updateTags();
662 updateBorder();
666 void Board::draggingOn(int pool, int index, const QPoint& point) {
667 Point to = converter()->toLogical(point);
669 if (m_sprites.valid(to))
670 switch(m_entity.lock()->validTurn(pool)) {
671 case Moving: {
672 DropUserMove m = m_entity.lock()->createDrop(pool, index, to);
673 AbstractMove::Ptr mv = m_entity.lock()->testMove(m);
674 if (mv) {
675 setTags("validmove", to);
676 return;
678 break;
681 case Premoving:
682 setTags("validmove", to);
683 return;
685 default:
686 break;
689 clearTags("validmove");
692 bool Board::dropOn(int pool, int index, const QPoint& point) {
694 Point to = converter()->toLogical(point);
695 if (!m_sprites.valid(to))
696 return false;
698 clearTags("validmove");
700 switch(m_entity.lock()->validTurn(pool)) {
702 case Moving: {
703 DropUserMove m = m_entity.lock()->createDrop(pool, index, to);
704 AbstractMove::Ptr mv = m_entity.lock()->testMove(m);
705 if (mv) {
706 m_entity.lock()->executeMove(mv);
707 return true;
709 break;
712 case Premoving: {
713 DropUserMove m = m_entity.lock()->createDrop(pool, index, to);
714 if (m_entity.lock()->testPremove(m)) {
715 m_entity.lock()->addPremove(m);
716 setPremove(m);
718 break;
721 default:
722 break;
725 std::cout << "invalid move" << std::endl;
726 error(InvalidMove);
727 return false;