push 5fde164f48250f9146308e3785b4be5065e285fd
[tagua/yd.git] / src / chessboard.cpp
blob7bf7a35f036cbceda35a8d3d2231cee3ff1d141c
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;
78 while(!m_canvas_border_text->items()->isEmpty())
79 delete m_canvas_border_text->items()->first();
80 delete m_canvas_border_text;
82 while(!m_pieces_group->items()->isEmpty())
83 delete m_pieces_group->items()->first();
84 delete m_pieces_group;
87 void ChessBoard::settingsChanged() {
88 m_anim_manager->reload();
89 m_border_text_color = m_controls_loader.getStaticValue<QColor>("border_color");
90 m_border_font = m_controls_loader.getStaticValue<QFont>("border_font");
92 recreateBorder();
95 void ChessBoard::updateBackground() {
96 while(!m_canvas_background->items()->isEmpty())
97 delete m_canvas_background->items()->first();
99 if(!m_square_size)
100 return;
102 Loader::PixmapOrMap bg = m_tags_loader.getValue<Loader::PixmapOrMap>("background");
103 if(const QPixmap* p = boost::get<QPixmap>(&bg)) {
104 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(*p, boardRect().size(), QPoint(),
105 true, m_canvas_background);
106 t->show();
108 else if(const Loader::PixmapMap* p = boost::get<Loader::PixmapMap>(&bg)) {
109 for(Loader::PixmapMap::const_iterator it = p->begin(); it != p->end(); ++it) {
110 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(it->second, it->first.size(),
111 QPoint(), true, m_canvas_background);
112 t->moveTo(it->first.topLeft());
113 t->show();
116 else
117 kError() << "Unexpected type in boost::variant";
120 void ChessBoard::adjustSprite(const Point& p, bool immediate) {
121 SpritePtr sprite = m_sprites[p].sprite();
123 if(!sprite)
124 return;
126 m_anim_manager->enqueue(
127 1 /*BROKEN m_anim_movement*/ && !immediate
128 ? AnimationPtr(new MovementAnimation(sprite, converter()->toReal(p), 1.0))
129 : AnimationPtr(new InstantAnimation(sprite, converter()->toReal(p)))
133 boost::shared_ptr<KGameCanvasPixmap> ChessBoard::addTag(const QString& name, Point pt, bool over) {
134 if(!m_sprites.valid(pt))
135 return boost::shared_ptr<KGameCanvasPixmap>();
137 QPixmap p = m_tags_loader.getPixmap(name);
138 boost::shared_ptr<KGameCanvasPixmap> item =
139 boost::shared_ptr<KGameCanvasPixmap>(new KGameCanvasPixmap(p, this));
140 item->moveTo(converter()->toReal(pt));
141 if(over)
142 item->stackOver(m_pieces_group);
143 else
144 item->stackUnder(m_pieces_group);
145 item->show();
147 (*m_tags)[name][pt] = item;
148 return item;
151 void ChessBoard::clearTags(const QString& name) {
152 m_tags->erase(name);
155 void ChessBoard::clearTags() {
156 m_tags->clear();
159 void ChessBoard::setTags(const QString& name, Point p1, Point p2, Point p3,
160 Point p4, Point p5, Point p6 ) {
161 //TODO: maybe this could be optimized a bit
162 clearTags(name);
163 addTag(name, p1);
164 addTag(name, p2);
165 addTag(name, p3);
166 addTag(name, p4);
167 addTag(name, p5);
168 addTag(name, p6);
171 void ChessBoard::recreateBorder() {
172 m_border_text.clear();
173 while(!m_canvas_border_text->items()->isEmpty())
174 delete m_canvas_border_text->items()->first();
176 if(m_border_coords.size() == 0)
177 return;
179 Point s = m_sprites.getSize();
180 for(int w = 0; w<2; w++)
181 for(int i = 0;i<s.x;i++) {
182 int c = w ? i : i+s.x+s.y;
183 QString l = m_border_coords.size()>c ? m_border_coords[c] : QString();
184 ConstrainedText *item = new ConstrainedText(m_canvas_border_text); // FIXME[vg]: leaked
185 item->setColor(m_border_text_color);
186 item->setText(l);
187 item->setFont(m_border_font);
188 item->setColor(m_border_text_color);
189 item->show();
190 m_border_text.push_back(item);
193 for(int w = 0; w<2; w++)
194 for(int i = 0;i<s.y;i++) {
195 int c = w ? i+s.x : i+2*s.x+s.y;
196 QString n = m_border_coords.size()>c ? m_border_coords[c] : QString();
197 ConstrainedText *item = new ConstrainedText(m_canvas_border_text); // FIXME[vg]: leaked
198 item->setColor(m_border_text_color);
199 item->setText(n);
200 item->setFont(m_border_font);
201 item->setColor(m_border_text_color);
202 item->show();
203 m_border_text.push_back(item);
206 m_pieces_group->raise();
208 updateBorder();
211 void ChessBoard::updateBorder() {
212 while(!m_canvas_border->items()->isEmpty())
213 delete m_canvas_border->items()->first();
215 if(!m_square_size)
216 return;
218 int at = 0;
219 for(int w = 0; w<2; w++)
220 for(int i = 0;i<m_sprites.getSize().x;i++) {
221 int x = (m_flipped ? (m_sprites.getSize().x-1-i) : i)*m_square_size;
222 int y = w ? -m_border_text_far : m_square_size*m_sprites.getSize().y+m_border_text_near;
224 m_border_text[at]->setVisible(m_border_text_near != m_border_text_far);
225 m_border_text[at]->setConstrainRect(QRect(x,y,m_square_size,m_border_text_far-m_border_text_near));
226 at++;
229 for(int w = 0; w<2; w++)
230 for(int i = 0;i<m_sprites.getSize().y;i++) {
231 int x = w ? (-m_border_text_far-m_border_text_near)/2
232 : m_square_size*m_sprites.getSize().x + (m_border_text_far+m_border_text_near)/2;
233 int y = (!m_flipped ? (m_sprites.getSize().y-1-i) : i)*m_square_size
234 + (m_square_size-m_border_text_far-m_border_text_near)/2;
236 m_border_text[at]->setVisible(m_border_text_near != m_border_text_far);
237 m_border_text[at]->setConstrainRect(QRect(x-m_square_size/2,y,m_square_size,m_border_text_far-m_border_text_near));
238 at++;
241 ::LuaApi::LuaValueMap params;
242 params["width"] = m_square_size*m_sprites.getSize().x;
243 params["height"] = m_square_size*m_sprites.getSize().y;
244 Loader::PixmapOrMap bord = m_controls_loader.getValue<Loader::PixmapOrMap>("border", &params);
245 if(const QPixmap* p = boost::get<QPixmap>(&bord)) {
246 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(*p, boardRect().size(), QPoint(),
247 true, m_canvas_border);
248 t->show();
250 else if(const Loader::PixmapMap* p = boost::get<Loader::PixmapMap>(&bord)) {
251 for(Loader::PixmapMap::const_iterator it = p->begin(); it != p->end(); ++it) {
252 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(it->second, it->first.size(),
253 QPoint(), true, m_canvas_border);
254 t->moveTo(it->first.topLeft());
255 t->show();
258 else
259 kError() << "Unexpected type in boost::variant";
262 void ChessBoard::createGrid(Point p, const QStringList& border_coords) {
263 m_border_coords = border_coords;
264 m_sprites = PieceGrid(p.x,p.y);
265 recreateBorder();
268 QPixmap ChessBoard::loadSprite(const QString& id) {
269 return m_loader.piecePixmap(id, m_flipped);
272 QRect ChessBoard::computeRect(Point p) const {
273 QPoint realPoint = converter()->toReal(p);
274 return squareRect(realPoint.x(), realPoint.y());
277 QRect ChessBoard::squareRect(int x, int y) const {
278 return QRect(x, y, m_square_size, m_square_size);
281 QRegion ChessBoard::computeRegion(Point p) const {
282 return QRegion(computeRect(p));
285 // selection
286 void ChessBoard::setSelection(const Point& p) {
287 lastSelection = selection;
288 selection = p;
289 setTags("selection", p);
292 void ChessBoard::cancelSelection() {
293 lastSelection = selection;
294 selection = Point::invalid();
295 clearTags("selection");
298 // premove
300 void ChessBoard::setPremove(const Move& premove) {
301 m_premove_from = premove.src();
302 m_premove_to = premove.dst();
303 setTags("premove", m_premove_from, m_premove_to);
306 void ChessBoard::cancelPremove() {
307 m_premove_from = Point::invalid();
308 m_premove_to = Point::invalid();
309 clearTags("premove");
312 void ChessBoard::updateSprites() {
313 // adjust piece positions
314 for (Point i = m_sprites.first(); i <= m_sprites.last(); i = m_sprites.next(i)) {
315 boost::shared_ptr<Sprite> p = m_sprites[i].sprite();
317 if (p) {
318 // drawing sprite
319 p->setPixmap(loadSprite(m_sprites[i].name()));
320 adjustSprite(i, true);
325 void ChessBoard::updateTags() {
326 if(!m_square_size)
327 return;
329 for(BoardTags::iterator tit = m_tags->begin(); tit != m_tags->end(); ++tit)
330 for(std::map<Point, boost::shared_ptr<KGameCanvasPixmap> >::iterator pt =
331 tit->second.begin(); pt != tit->second.end(); ++pt) {
332 pt->second->moveTo(converter()->toReal(pt->first));
333 pt->second->setPixmap(m_tags_loader.getPixmap(tit->first));
338 bool ChessBoard::doMove(Move& m) {
339 if (m_entity.lock()->oneClickMoves() || m_entity.lock()->validTurn(m.src()) == Moving) {
340 if (m_entity.lock()->testMove(m)) {
341 m_entity.lock()->executeMove(m);
342 return true;
346 kDebug() << "invalid move";
347 error(InvalidMove);
349 return false;
353 void ChessBoard::onResize(int new_size, int border_size, int border_text_near,
354 int border_text_far, bool force_reload) {
355 if(m_square_size == new_size && !force_reload)
356 return;
358 m_square_size = new_size;
359 m_border_size = border_size;
360 m_border_text_near = border_text_near;
361 m_border_text_far = border_text_far;
363 // update the size of the piece loader
364 m_loader.setSize(m_square_size);
366 // update the size of the tag loader
367 m_tags_loader.setSize(m_square_size);
369 // update the size of the controls loader
370 m_controls_loader.setSize(m_border_size);
372 // update canvas background
373 updateBackground();
375 // update the sprites
376 updateSprites();
378 // update piece tags
379 updateTags();
381 // update border
382 updateBorder();
385 void ChessBoard::onMousePress(const QPoint& pos, int button) {
386 Point point = converter()->toLogical(pos);
387 if (!m_sprites.valid(point))
388 return;
390 //BEGIN left click
392 if (button == Qt::LeftButton) {
393 if (m_entity.lock()->oneClickMoves()) {
394 Move m(Point::invalid(), point);
395 doMove(m);
397 else {
398 shared_ptr<Sprite> piece = m_sprites[point].sprite();
400 if (piece && m_entity.lock()->movable(point)) {
401 cancelSelection();
402 m_drag_info = DragInfoPtr(new DragInfo(point, pos, piece,
403 m_entity.lock()->validTurn(point)) );
404 piece->raise();
407 // if selection is valid, (pre)move to point
408 else if (selection != Point::invalid()) {
409 piece = m_sprites[selection].sprite();
410 Q_ASSERT(piece);
411 Move m(selection, point);
413 switch(m_entity.lock()->validTurn(selection)) {
415 case Moving:
416 doMove(m);
417 cancelSelection();
418 break;
420 case Premoving:
421 if (m_entity.lock()->testPremove(m)) {
422 m_entity.lock()->addPremove(m);
423 setPremove(m);
424 cancelSelection();
426 break;
428 default:
429 break;
435 //END left click
437 //BEGIN right click
439 else if (button == Qt::RightButton) {
440 cancelSelection();
441 if (point == m_premove_from || point == m_premove_to)
442 cancelPremove();
443 m_entity.lock()->handleRightClick(point);
446 //END right click
449 void ChessBoard::onMouseRelease(const QPoint& pos, int button) {
450 Point point = converter()->toLogical(pos);
452 //BEGIN left click
454 if (button == Qt::LeftButton) {
456 if (m_drag_info) {
457 // Q_ASSERT(m_drag_info->piece);
458 Q_ASSERT(m_drag_info->sprite);
459 bool moved = false;
461 // remove valid move highlighting
462 clearTags("validmove");
464 // toggle selection if the piece didn't move
465 if (m_drag_info->from == point) {
466 if (lastSelection == point)
467 cancelSelection();
468 else
469 setSelection(point);
472 else {
473 Point dst = point;
474 if (!m_sprites.valid(point))
475 dst = Point::invalid();
476 Move m(m_drag_info->from, dst);
478 switch (m_entity.lock()->validTurn(m.src())) {
479 case Moving:
480 if (doMove(m))
481 moved = true;
482 break;
484 case Premoving:
485 if (m_entity.lock()->testPremove(m)) {
486 m_entity.lock()->addPremove(m);
487 setPremove(m);
489 break;
491 default:
492 break;
496 shared_ptr<Sprite> s = m_sprites[m_drag_info->from].sprite();
497 if (!moved && s && s->pos() != converter()->toReal(m_drag_info->from)) {
498 Q_ASSERT(s);
499 QPoint real = converter()->toReal(m_drag_info->from);
500 if( (point == m_drag_info->from) ? 0/* !m_anim_movement*/ : 0 /* !m_anim_fade*/) //BROKEN
501 m_anim_manager->enqueue(shared_ptr<Animation>(new InstantAnimation(s, real)));
502 else if (point == m_drag_info->from)
503 m_anim_manager->enqueue(shared_ptr<Animation>(new MovementAnimation(s, real)));
504 else
505 m_anim_manager->enqueue(shared_ptr<Animation>(new TeleportAnimation(s, s->pos(), real)));
508 m_drag_info = DragInfoPtr();
512 //END left button
515 void ChessBoard::onMouseMove(const QPoint& pos, int /*button*/) {
516 Point point = converter()->toLogical(pos);
517 static Point cachedPoint = Point();
518 static bool cachedValid;
520 if (m_drag_info) {
521 Q_ASSERT(m_drag_info->sprite);
522 // check drag threshold
523 if (!m_drag_info->dragStarted) {
524 QPoint delta = pos - m_drag_info->real;
525 if (delta.x() * delta.x() + delta.y() * delta.y() > DragInfo::DRAG_THRESHOLD) {
526 m_drag_info->dragStarted = true;
529 if (m_drag_info->dragStarted)
530 m_drag_info->sprite->moveTo(pos - QPoint(m_square_size / 2, m_square_size / 2) );
532 // highlight valid moves
533 Move move(m_drag_info->from, point);
534 bool valid = m_sprites.valid(point);
535 if (valid) {
536 InteractionType action = m_entity.lock()->validTurn(move.src());
537 if (action == Moving)
538 if (point == cachedPoint)
539 valid = cachedValid;
540 else {
541 valid = m_entity.lock()->testMove(move);
542 cachedPoint = point;
543 cachedValid = valid;
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 Piece hint;
558 if (m_sprites.valid(point)) {
559 Move move(Point::invalid(), point);
560 if (m_entity.lock()->testMove(move)) {
561 // set move hint
562 hint = m_entity.lock()->moveHint(move);
566 updateHinting(point, hint);
570 void ChessBoard::onPositionChanged() {
571 if (m_entity.lock() && m_entity.lock()->oneClickMoves() && m_sprites.valid(m_hinting_pos)) {
572 Piece hint;
574 Move move(Point::invalid(), m_hinting_pos);
575 if (m_entity.lock()->testMove(move)) {
576 // set move hint
577 hint = m_entity.lock()->moveHint(move);
580 updateHinting(move.dst(), hint);
584 void ChessBoard::onMouseLeave() {
585 updateHinting(Point::invalid(), Piece());
588 void ChessBoard::updateHinting(Point pt, Piece piece) {
589 if(!m_sprites.valid(pt))
590 piece = Piece();
593 QString piece_name = m_namer->name(piece);
594 if(piece != Piece() || !m_sprites.valid(pt)) {
595 if(m_hinting.sprite()) {
596 if(1 /*BROKEN m_anim_fade*/)
597 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
598 m_hinting.sprite()->pos(), 160, 0)) );
599 else
600 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new CaptureAnimation(m_hinting.sprite())) );
603 m_hinting_pos = Point::invalid();
604 m_hinting = NamedSprite();
606 else {
607 if(pt == m_hinting_pos) {
608 if (piece_name != m_hinting.name()) {
609 m_hinting = NamedSprite(piece_name, m_hinting.sprite());
610 m_hinting.sprite()->setPixmap(loadSprite(piece_name));
613 else {
614 if(m_hinting.sprite()) {
615 if(1 /*BROKEN m_anim_fade*/)
616 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
617 m_hinting.sprite()->pos(), 160, 0)) );
618 else
619 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new CaptureAnimation(m_hinting.sprite())) );
622 QPixmap pix = loadSprite(piece_name);
623 SpritePtr sprite(new Sprite(pix, piecesGroup(), converter()->toReal(pt)));
624 sprite->setOpacity(160);
625 sprite->raise();
626 sprite->show();
628 m_hinting_pos = pt;
629 m_hinting = NamedSprite(piece_name, sprite);
631 /*if(m_anim_fade)
632 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
633 m_hinting.sprite()->pos(), 0, 160)) );
634 else {
635 m_hinting.sprite()->setOpacity(160);
636 m_anim_manager->enqueue(boost::shared_ptr<Animation>(new DropAnimation(m_hinting.sprite())) );
642 void ChessBoard::reset() {
643 clearTags();
644 cancelSelection();
645 cancelPremove();
646 m_anim_manager->stop();
649 void ChessBoard::flip(bool flipped)
651 if (m_flipped != flipped) {
652 m_flipped = flipped;
654 // update sprite positions
655 for (Point i = m_sprites.first(); i <= m_sprites.last(); i = m_sprites.next(i)) {
656 SpritePtr p = m_sprites[i].sprite();
657 if (p) {
658 p->setPixmap(loadSprite(m_sprites[i].name()));
659 adjustSprite(i, true);
663 updateTags();
664 updateBorder();
668 void ChessBoard::draggingOn(const IColor* pool, int index, const QPoint& point) {
669 Point to = converter()->toLogical(point);
671 if (m_sprites.valid(to))
672 switch (m_entity.lock()->validTurn(pool)) {
673 case Moving: {
674 Move m(pool, index, to);
675 if (m_entity.lock()->testMove(m)) {
676 setTags("validmove", to);
677 return;
679 break;
682 case Premoving:
683 setTags("validmove", to);
684 return;
686 default:
687 break;
690 clearTags("validmove");
693 bool ChessBoard::dropOn(const IColor* pool, int index, const QPoint& point) {
695 Point to = converter()->toLogical(point);
696 if (!m_sprites.valid(to))
697 return false;
699 clearTags("validmove");
701 switch (m_entity.lock()->validTurn(pool)) {
703 case Moving: {
704 Move m(pool, index, to);
705 if (m_entity.lock()->testMove(m)) {
706 m_entity.lock()->executeMove(m);
707 return true;
709 break;
712 case Premoving: {
713 Move m(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 kDebug() << "invalid move";
726 error(InvalidMove);
727 return false;