Merge from master bf883501679502d078aa719efed53ca4b2d4328c.
[tagua/yd.git] / src / chessboard.cpp
blobc94e52cb2625dae759d65299fad0e0a046cc60b5
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 <QPainter>
12 #include <QApplication>
13 #include <QMouseEvent>
14 #include <KDebug>
16 #include <core/namer.h>
18 #include "animation.h"
19 #include "animationmanager.h"
20 #include "chessboard.h"
21 #include "components.h"
22 #include "constrainedtext.h"
23 #include "entities/userentity.h"
24 #include "mastersettings.h"
25 #include "sprite.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 ChessBoard::ChessBoard(Components* components,
34 AnimationManager* animManager,
35 KGameCanvasAbstract* parent)
36 : ClickableCanvas(parent)
37 , m_flipped(false)
38 , m_square_size(0)
39 , m_border_size(0)
40 , m_sprites(0,0)
41 , m_hinting_pos(Point::invalid())
42 , selection(Point::invalid())
43 , lastSelection(Point::invalid())
44 , m_dropped_pool(0)
45 , m_dropped_index(-1)
46 , m_anim_manager(animManager) {
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 m_namer = components->namer();
64 settingsChanged();
67 ChessBoard::~ChessBoard() {
68 delete m_pieces_group;
70 while(!m_canvas_background->items()->isEmpty())
71 delete m_canvas_background->items()->first();
72 delete m_canvas_background;
74 while(!m_canvas_border->items()->isEmpty())
75 delete m_canvas_border->items()->first();
76 delete m_canvas_border;
79 void ChessBoard::settingsChanged() {
80 m_anim_manager->reload();
81 m_border_text_color = m_controls_loader.getStaticValue<QColor>("border_color");
82 m_border_font = m_controls_loader.getStaticValue<QFont>("border_font");
84 recreateBorder();
87 void ChessBoard::updateBackground() {
88 while(!m_canvas_background->items()->isEmpty())
89 delete m_canvas_background->items()->first();
91 if(!m_square_size)
92 return;
94 Loader::PixmapOrMap bg = m_tags_loader.getValue<Loader::PixmapOrMap>("background");
95 if(const QPixmap* p = boost::get<QPixmap>(&bg)) {
96 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(*p, boardRect().size(), QPoint(),
97 true, m_canvas_background);
98 t->show();
100 else if(const Loader::PixmapMap* p = boost::get<Loader::PixmapMap>(&bg)) {
101 for(Loader::PixmapMap::const_iterator it = p->begin(); it != p->end(); ++it) {
102 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(it->second, it->first.size(),
103 QPoint(), true, m_canvas_background);
104 t->moveTo(it->first.topLeft());
105 t->show();
108 else
109 kError() << "Unexpected type in boost::variant";
112 void ChessBoard::adjustSprite(const Point& p, bool immediate) {
113 SpritePtr sprite = m_sprites[p].sprite();
115 if(!sprite)
116 return;
118 m_anim_manager->enqueue(
119 1 /*BROKEN m_anim_movement*/ && !immediate
120 ? AnimationPtr(new MovementAnimation(sprite, converter()->toReal(p), 1.0))
121 : AnimationPtr(new InstantAnimation(sprite, converter()->toReal(p)))
125 boost::shared_ptr<KGameCanvasPixmap> ChessBoard::addTag(const QString& name, Point pt, bool over) {
126 if(!m_sprites.valid(pt))
127 return boost::shared_ptr<KGameCanvasPixmap>();
129 QPixmap p = m_tags_loader.getPixmap(name);
130 boost::shared_ptr<KGameCanvasPixmap> item =
131 boost::shared_ptr<KGameCanvasPixmap>(new KGameCanvasPixmap(p, this));
132 item->moveTo(converter()->toReal(pt));
133 if(over)
134 item->stackOver(m_pieces_group);
135 else
136 item->stackUnder(m_pieces_group);
137 item->show();
139 (*m_tags)[name][pt] = item;
140 return item;
143 void ChessBoard::clearTags(const QString& name) {
144 m_tags->erase(name);
147 void ChessBoard::clearTags() {
148 m_tags->clear();
151 void ChessBoard::setTags(const QString& name, Point p1, Point p2, Point p3,
152 Point p4, Point p5, Point p6 ) {
153 //TODO: maybe this could be optimized a bit
154 clearTags(name);
155 addTag(name, p1);
156 addTag(name, p2);
157 addTag(name, p3);
158 addTag(name, p4);
159 addTag(name, p5);
160 addTag(name, p6);
163 void ChessBoard::recreateBorder() {
164 m_border_text.clear();
165 while(!m_canvas_border_text->items()->isEmpty())
166 delete m_canvas_border_text->items()->first();
168 if(m_border_coords.size() == 0)
169 return;
171 Point s = m_sprites.getSize();
172 for(int w = 0; w<2; w++)
173 for(int i = 0;i<s.x;i++) {
174 int c = w ? i : i+s.x+s.y;
175 QString l = m_border_coords.size()>c ? m_border_coords[c] : QString();
176 ConstrainedText *item = new ConstrainedText(m_canvas_border_text);
177 item->setColor(m_border_text_color);
178 item->setText(l);
179 item->setFont(m_border_font);
180 item->setColor(m_border_text_color);
181 item->show();
182 m_border_text.push_back(item);
185 for(int w = 0; w<2; w++)
186 for(int i = 0;i<s.y;i++) {
187 int c = w ? i+s.x : i+2*s.x+s.y;
188 QString n = m_border_coords.size()>c ? m_border_coords[c] : QString();
189 ConstrainedText *item = new ConstrainedText(m_canvas_border_text);
190 item->setColor(m_border_text_color);
191 item->setText(n);
192 item->setFont(m_border_font);
193 item->setColor(m_border_text_color);
194 item->show();
195 m_border_text.push_back(item);
198 m_pieces_group->raise();
200 updateBorder();
203 void ChessBoard::updateBorder() {
204 while(!m_canvas_border->items()->isEmpty())
205 delete m_canvas_border->items()->first();
207 if(!m_square_size)
208 return;
210 int at = 0;
211 for(int w = 0; w<2; w++)
212 for(int i = 0;i<m_sprites.getSize().x;i++) {
213 int x = (m_flipped ? (m_sprites.getSize().x-1-i) : i)*m_square_size;
214 int y = w ? -m_border_text_far : m_square_size*m_sprites.getSize().y+m_border_text_near;
216 m_border_text[at]->setVisible(m_border_text_near != m_border_text_far);
217 m_border_text[at]->setConstrainRect(QRect(x,y,m_square_size,m_border_text_far-m_border_text_near));
218 at++;
221 for(int w = 0; w<2; w++)
222 for(int i = 0;i<m_sprites.getSize().y;i++) {
223 int x = w ? (-m_border_text_far-m_border_text_near)/2
224 : m_square_size*m_sprites.getSize().x + (m_border_text_far+m_border_text_near)/2;
225 int y = (!m_flipped ? (m_sprites.getSize().y-1-i) : i)*m_square_size
226 + (m_square_size-m_border_text_far-m_border_text_near)/2;
228 m_border_text[at]->setVisible(m_border_text_near != m_border_text_far);
229 m_border_text[at]->setConstrainRect(QRect(x-m_square_size/2,y,m_square_size,m_border_text_far-m_border_text_near));
230 at++;
233 ::LuaApi::LuaValueMap params;
234 params["width"] = m_square_size*m_sprites.getSize().x;
235 params["height"] = m_square_size*m_sprites.getSize().y;
236 Loader::PixmapOrMap bord = m_controls_loader.getValue<Loader::PixmapOrMap>("border", &params);
237 if(const QPixmap* p = boost::get<QPixmap>(&bord)) {
238 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(*p, boardRect().size(), QPoint(),
239 true, m_canvas_border);
240 t->show();
242 else if(const Loader::PixmapMap* p = boost::get<Loader::PixmapMap>(&bord)) {
243 for(Loader::PixmapMap::const_iterator it = p->begin(); it != p->end(); ++it) {
244 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(it->second, it->first.size(),
245 QPoint(), true, m_canvas_border);
246 t->moveTo(it->first.topLeft());
247 t->show();
250 else
251 kError() << "Unexpected type in boost::variant";
254 void ChessBoard::createGrid(Point p, const QStringList& border_coords) {
255 m_border_coords = border_coords;
256 m_sprites = PieceGrid(p.x,p.y);
257 recreateBorder();
260 QPixmap ChessBoard::loadSprite(const QString& id) {
261 return m_loader.piecePixmap(id, m_flipped);
264 QRect ChessBoard::computeRect(Point p) const {
265 QPoint realPoint = converter()->toReal(p);
266 return squareRect(realPoint.x(), realPoint.y());
269 QRect ChessBoard::squareRect(int x, int y) const {
270 return QRect(x, y, m_square_size, m_square_size);
273 QRegion ChessBoard::computeRegion(Point p) const {
274 return QRegion(computeRect(p));
277 // selection
278 void ChessBoard::setSelection(const Point& p) {
279 lastSelection = selection;
280 selection = p;
281 setTags("selection", p);
284 void ChessBoard::cancelSelection() {
285 lastSelection = selection;
286 selection = Point::invalid();
287 clearTags("selection");
290 // premove
292 void ChessBoard::setPremove(const Move& premove) {
293 m_premove_from = premove.src();
294 m_premove_to = premove.dst();
295 setTags("premove", m_premove_from, m_premove_to);
298 void ChessBoard::cancelPremove() {
299 m_premove_from = Point::invalid();
300 m_premove_to = Point::invalid();
301 clearTags("premove");
304 void ChessBoard::updateSprites() {
305 // adjust piece positions
306 for (Point i = m_sprites.first(); i <= m_sprites.last(); i = m_sprites.next(i)) {
307 boost::shared_ptr<Sprite> p = m_sprites[i].sprite();
309 if (p) {
310 // drawing sprite
311 p->setPixmap(loadSprite(m_sprites[i].name()));
312 adjustSprite(i, true);
317 void ChessBoard::updateTags() {
318 if(!m_square_size)
319 return;
321 for(BoardTags::iterator tit = m_tags->begin(); tit != m_tags->end(); ++tit)
322 for(std::map<Point, boost::shared_ptr<KGameCanvasPixmap> >::iterator pt =
323 tit->second.begin(); pt != tit->second.end(); ++pt) {
324 pt->second->moveTo(converter()->toReal(pt->first));
325 pt->second->setPixmap(m_tags_loader.getPixmap(tit->first));
330 bool ChessBoard::doMove(Move& m) {
331 if (m_entity.lock()->oneClickMoves() || m_entity.lock()->validTurn(m.src()) == Moving) {
332 if (m_entity.lock()->testMove(m)) {
333 m_entity.lock()->executeMove(m);
334 return true;
338 kDebug() << "invalid move";
339 error(InvalidMove);
341 return false;
345 void ChessBoard::onResize(int new_size, int border_size, int border_text_near,
346 int border_text_far, bool force_reload) {
347 if(m_square_size == new_size && !force_reload)
348 return;
350 m_square_size = new_size;
351 m_border_size = border_size;
352 m_border_text_near = border_text_near;
353 m_border_text_far = border_text_far;
355 // update the size of the piece loader
356 m_loader.setSize(m_square_size);
358 // update the size of the tag loader
359 m_tags_loader.setSize(m_square_size);
361 // update the size of the controls loader
362 m_controls_loader.setSize(m_border_size);
364 // update canvas background
365 updateBackground();
367 // update the sprites
368 updateSprites();
370 // update piece tags
371 updateTags();
373 // update border
374 updateBorder();
377 void ChessBoard::onMousePress(const QPoint& pos, int button) {
378 Point point = converter()->toLogical(pos);
379 if (!m_sprites.valid(point))
380 return;
382 //BEGIN left click
384 if (button == Qt::LeftButton) {
385 if (m_entity.lock()->oneClickMoves()) {
386 Move m(Point::invalid(), point);
387 doMove(m);
389 else {
390 shared_ptr<Sprite> piece = m_sprites[point].sprite();
392 if (piece && m_entity.lock()->movable(point)) {
393 cancelSelection();
394 m_drag_info = DragInfoPtr(new DragInfo(point, pos, piece,
395 m_entity.lock()->validTurn(point)) );
396 piece->raise();
399 // if selection is valid, (pre)move to point
400 else if (selection != Point::invalid()) {
401 piece = m_sprites[selection].sprite();
402 Q_ASSERT(piece);
403 Move m(selection, point);
405 switch(m_entity.lock()->validTurn(selection)) {
407 case Moving:
408 doMove(m);
409 cancelSelection();
410 break;
412 case Premoving:
413 if (m_entity.lock()->testPremove(m)) {
414 m_entity.lock()->addPremove(m);
415 setPremove(m);
416 cancelSelection();
418 break;
420 default:
421 break;
427 //END left click
429 //BEGIN right click
431 else if (button == Qt::RightButton) {
432 cancelSelection();
433 if (point == m_premove_from || point == m_premove_to)
434 cancelPremove();
435 m_entity.lock()->handleRightClick(point);
438 //END right click
441 void ChessBoard::onMouseRelease(const QPoint& pos, int button) {
442 Point point = converter()->toLogical(pos);
444 //BEGIN left click
446 if (button == Qt::LeftButton) {
448 if (m_drag_info) {
449 // Q_ASSERT(m_drag_info->piece);
450 Q_ASSERT(m_drag_info->sprite);
451 bool moved = false;
453 // remove valid move highlighting
454 clearTags("validmove");
456 // toggle selection if the piece didn't move
457 if (m_drag_info->from == point) {
458 if (lastSelection == point)
459 cancelSelection();
460 else
461 setSelection(point);
464 else {
465 Point dst = point;
466 if (!m_sprites.valid(point))
467 dst = Point::invalid();
468 Move m(m_drag_info->from, dst);
470 switch (m_entity.lock()->validTurn(m.src())) {
471 case Moving:
472 if (doMove(m))
473 moved = true;
474 break;
476 case Premoving:
477 if (m_entity.lock()->testPremove(m)) {
478 m_entity.lock()->addPremove(m);
479 setPremove(m);
481 break;
483 default:
484 break;
488 shared_ptr<Sprite> s = m_sprites[m_drag_info->from].sprite();
489 if (!moved && s && s->pos() != converter()->toReal(m_drag_info->from)) {
490 Q_ASSERT(s);
491 QPoint real = converter()->toReal(m_drag_info->from);
492 if( (point == m_drag_info->from) ? 0/* !m_anim_movement*/ : 0 /* !m_anim_fade*/) //BROKEN
493 m_anim_manager->enqueue(shared_ptr<Animation>(new InstantAnimation(s, real)));
494 else if (point == m_drag_info->from)
495 m_anim_manager->enqueue(shared_ptr<Animation>(new MovementAnimation(s, real)));
496 else
497 m_anim_manager->enqueue(shared_ptr<Animation>(new TeleportAnimation(s, s->pos(), real)));
500 m_drag_info = DragInfoPtr();
504 //END left button
507 void ChessBoard::onMouseMove(const QPoint& pos, int /*button*/) {
508 Point point = converter()->toLogical(pos);
510 if (m_drag_info) {
511 Q_ASSERT(m_drag_info->sprite);
512 // check drag threshold
513 if (!m_drag_info->dragStarted) {
514 QPoint delta = pos - m_drag_info->real;
515 if (delta.x() * delta.x() + delta.y() * delta.y() > DragInfo::DRAG_THRESHOLD) {
516 m_drag_info->dragStarted = true;
519 if (m_drag_info->dragStarted)
520 m_drag_info->sprite->moveTo(pos - QPoint(m_square_size / 2, m_square_size / 2) );
522 // highlight valid moves
523 Move move(m_drag_info->from, point);
524 bool valid = m_sprites.valid(point);
525 if (valid) {
526 InteractionType action = m_entity.lock()->validTurn(move.src());
527 if (action == Moving)
528 valid = m_entity.lock()->testMove(move);
531 if (valid)
532 setTags("validmove", point);
533 else
534 clearTags("validmove");
536 else if (m_entity.lock()->oneClickMoves()) {
537 if(point == m_hinting_pos)
538 return;
540 Piece hint;
542 if (m_sprites.valid(point)) {
543 Move move(Point::invalid(), point);
544 if (m_entity.lock()->testMove(move)) {
545 // set move hint
546 hint = m_entity.lock()->moveHint(move);
550 updateHinting(point, hint);
554 void ChessBoard::onPositionChanged() {
555 if (m_entity.lock() && m_entity.lock()->oneClickMoves() && m_sprites.valid(m_hinting_pos)) {
556 Piece hint;
558 Move move(Point::invalid(), m_hinting_pos);
559 if (m_entity.lock()->testMove(move)) {
560 // set move hint
561 hint = m_entity.lock()->moveHint(move);
564 updateHinting(move.dst(), hint);
568 void ChessBoard::onMouseLeave() {
569 updateHinting(Point::invalid(), Piece());
572 void ChessBoard::updateHinting(Point pt, Piece piece) {
573 if(!m_sprites.valid(pt))
574 piece = Piece();
577 QString piece_name = m_namer->name(piece);
578 if(piece != Piece() || !m_sprites.valid(pt)) {
579 if(m_hinting.sprite()) {
580 if(1 /*BROKEN m_anim_fade*/)
581 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
582 m_hinting.sprite()->pos(), 160, 0)) );
583 else
584 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new CaptureAnimation(m_hinting.sprite())) );
587 m_hinting_pos = Point::invalid();
588 m_hinting = NamedSprite();
590 else {
591 if(pt == m_hinting_pos) {
592 if (piece_name != m_hinting.name()) {
593 m_hinting = NamedSprite(piece_name, m_hinting.sprite());
594 m_hinting.sprite()->setPixmap(loadSprite(piece_name));
597 else {
598 if(m_hinting.sprite()) {
599 if(1 /*BROKEN m_anim_fade*/)
600 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
601 m_hinting.sprite()->pos(), 160, 0)) );
602 else
603 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new CaptureAnimation(m_hinting.sprite())) );
606 QPixmap pix = loadSprite(piece_name);
607 SpritePtr sprite(new Sprite(pix, piecesGroup(), converter()->toReal(pt)));
608 sprite->setOpacity(160);
609 sprite->raise();
610 sprite->show();
612 m_hinting_pos = pt;
613 m_hinting = NamedSprite(piece_name, sprite);
615 /*if(m_anim_fade)
616 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
617 m_hinting.sprite()->pos(), 0, 160)) );
618 else {
619 m_hinting.sprite()->setOpacity(160);
620 m_anim_manager->enqueue(boost::shared_ptr<Animation>(new DropAnimation(m_hinting.sprite())) );
626 void ChessBoard::reset() {
627 clearTags();
628 cancelSelection();
629 cancelPremove();
630 m_anim_manager->stop();
633 void ChessBoard::flip(bool flipped)
635 if (m_flipped != flipped) {
636 m_flipped = flipped;
638 // update sprite positions
639 for (Point i = m_sprites.first(); i <= m_sprites.last(); i = m_sprites.next(i)) {
640 SpritePtr p = m_sprites[i].sprite();
641 if (p) {
642 p->setPixmap(loadSprite(m_sprites[i].name()));
643 adjustSprite(i, true);
647 updateTags();
648 updateBorder();
652 void ChessBoard::draggingOn(const IColor* pool, int index, const QPoint& point) {
653 Point to = converter()->toLogical(point);
655 if (m_sprites.valid(to))
656 switch (m_entity.lock()->validTurn(pool)) {
657 case Moving: {
658 Move m(pool, index, to);
659 if (m_entity.lock()->testMove(m)) {
660 setTags("validmove", to);
661 return;
663 break;
666 case Premoving:
667 setTags("validmove", to);
668 return;
670 default:
671 break;
674 clearTags("validmove");
677 bool ChessBoard::dropOn(const IColor* pool, int index, const QPoint& point) {
679 Point to = converter()->toLogical(point);
680 if (!m_sprites.valid(to))
681 return false;
683 clearTags("validmove");
685 switch (m_entity.lock()->validTurn(pool)) {
687 case Moving: {
688 Move m(pool, index, to);
689 if (m_entity.lock()->testMove(m)) {
690 m_entity.lock()->executeMove(m);
691 return true;
693 break;
696 case Premoving: {
697 Move m(pool, index, to);
698 if (m_entity.lock()->testPremove(m)) {
699 m_entity.lock()->addPremove(m);
700 setPremove(m);
702 break;
705 default:
706 break;
709 kDebug() << "invalid move";
710 error(InvalidMove);
711 return false;