Tentative Randomless-Entropy variant.
[tagua/yd.git] / src / chessboard.cpp
blob2c90053195b64eeadaf8a646952907ee5b62ed58
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;
83 void ChessBoard::settingsChanged() {
84 m_anim_manager->reload();
85 m_border_text_color = m_controls_loader.getStaticValue<QColor>("border_color");
86 m_border_font = m_controls_loader.getStaticValue<QFont>("border_font");
88 recreateBorder();
91 void ChessBoard::updateBackground() {
92 while(!m_canvas_background->items()->isEmpty())
93 delete m_canvas_background->items()->first();
95 if(!m_square_size)
96 return;
98 Loader::PixmapOrMap bg = m_tags_loader.getValue<Loader::PixmapOrMap>("background");
99 if(const QPixmap* p = boost::get<QPixmap>(&bg)) {
100 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(*p, boardRect().size(), QPoint(),
101 true, m_canvas_background);
102 t->show();
104 else if(const Loader::PixmapMap* p = boost::get<Loader::PixmapMap>(&bg)) {
105 for(Loader::PixmapMap::const_iterator it = p->begin(); it != p->end(); ++it) {
106 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(it->second, it->first.size(),
107 QPoint(), true, m_canvas_background);
108 t->moveTo(it->first.topLeft());
109 t->show();
112 else
113 kError() << "Unexpected type in boost::variant";
116 void ChessBoard::adjustSprite(const Point& p, bool immediate) {
117 SpritePtr sprite = m_sprites[p].sprite();
119 if(!sprite)
120 return;
122 m_anim_manager->enqueue(
123 1 /*BROKEN m_anim_movement*/ && !immediate
124 ? AnimationPtr(new MovementAnimation(sprite, converter()->toReal(p), 1.0))
125 : AnimationPtr(new InstantAnimation(sprite, converter()->toReal(p)))
129 boost::shared_ptr<KGameCanvasPixmap> ChessBoard::addTag(const QString& name, Point pt, bool over) {
130 if(!m_sprites.valid(pt))
131 return boost::shared_ptr<KGameCanvasPixmap>();
133 QPixmap p = m_tags_loader.getPixmap(name);
134 boost::shared_ptr<KGameCanvasPixmap> item =
135 boost::shared_ptr<KGameCanvasPixmap>(new KGameCanvasPixmap(p, this));
136 item->moveTo(converter()->toReal(pt));
137 if(over)
138 item->stackOver(m_pieces_group);
139 else
140 item->stackUnder(m_pieces_group);
141 item->show();
143 (*m_tags)[name][pt] = item;
144 return item;
147 void ChessBoard::clearTags(const QString& name) {
148 m_tags->erase(name);
151 void ChessBoard::clearTags() {
152 m_tags->clear();
155 void ChessBoard::setTags(const QString& name, Point p1, Point p2, Point p3,
156 Point p4, Point p5, Point p6 ) {
157 //TODO: maybe this could be optimized a bit
158 clearTags(name);
159 addTag(name, p1);
160 addTag(name, p2);
161 addTag(name, p3);
162 addTag(name, p4);
163 addTag(name, p5);
164 addTag(name, p6);
167 void ChessBoard::recreateBorder() {
168 m_border_text.clear();
169 while(!m_canvas_border_text->items()->isEmpty())
170 delete m_canvas_border_text->items()->first();
172 if(m_border_coords.size() == 0)
173 return;
175 Point s = m_sprites.getSize();
176 for(int w = 0; w<2; w++)
177 for(int i = 0;i<s.x;i++) {
178 int c = w ? i : i+s.x+s.y;
179 QString l = m_border_coords.size()>c ? m_border_coords[c] : QString();
180 ConstrainedText *item = new ConstrainedText(m_canvas_border_text); // FIXME[vg]: leaked
181 item->setColor(m_border_text_color);
182 item->setText(l);
183 item->setFont(m_border_font);
184 item->setColor(m_border_text_color);
185 item->show();
186 m_border_text.push_back(item);
189 for(int w = 0; w<2; w++)
190 for(int i = 0;i<s.y;i++) {
191 int c = w ? i+s.x : i+2*s.x+s.y;
192 QString n = m_border_coords.size()>c ? m_border_coords[c] : QString();
193 ConstrainedText *item = new ConstrainedText(m_canvas_border_text); // FIXME[vg]: leaked
194 item->setColor(m_border_text_color);
195 item->setText(n);
196 item->setFont(m_border_font);
197 item->setColor(m_border_text_color);
198 item->show();
199 m_border_text.push_back(item);
202 m_pieces_group->raise();
204 updateBorder();
207 void ChessBoard::updateBorder() {
208 while(!m_canvas_border->items()->isEmpty())
209 delete m_canvas_border->items()->first();
211 if(!m_square_size)
212 return;
214 int at = 0;
215 for(int w = 0; w<2; w++)
216 for(int i = 0;i<m_sprites.getSize().x;i++) {
217 int x = (m_flipped ? (m_sprites.getSize().x-1-i) : i)*m_square_size;
218 int y = w ? -m_border_text_far : m_square_size*m_sprites.getSize().y+m_border_text_near;
220 m_border_text[at]->setVisible(m_border_text_near != m_border_text_far);
221 m_border_text[at]->setConstrainRect(QRect(x,y,m_square_size,m_border_text_far-m_border_text_near));
222 at++;
225 for(int w = 0; w<2; w++)
226 for(int i = 0;i<m_sprites.getSize().y;i++) {
227 int x = w ? (-m_border_text_far-m_border_text_near)/2
228 : m_square_size*m_sprites.getSize().x + (m_border_text_far+m_border_text_near)/2;
229 int y = (!m_flipped ? (m_sprites.getSize().y-1-i) : i)*m_square_size
230 + (m_square_size-m_border_text_far-m_border_text_near)/2;
232 m_border_text[at]->setVisible(m_border_text_near != m_border_text_far);
233 m_border_text[at]->setConstrainRect(QRect(x-m_square_size/2,y,m_square_size,m_border_text_far-m_border_text_near));
234 at++;
237 ::LuaApi::LuaValueMap params;
238 params["width"] = m_square_size*m_sprites.getSize().x;
239 params["height"] = m_square_size*m_sprites.getSize().y;
240 Loader::PixmapOrMap bord = m_controls_loader.getValue<Loader::PixmapOrMap>("border", &params);
241 if(const QPixmap* p = boost::get<QPixmap>(&bord)) {
242 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(*p, boardRect().size(), QPoint(),
243 true, m_canvas_border);
244 t->show();
246 else if(const Loader::PixmapMap* p = boost::get<Loader::PixmapMap>(&bord)) {
247 for(Loader::PixmapMap::const_iterator it = p->begin(); it != p->end(); ++it) {
248 KGameCanvasTiledPixmap *t = new KGameCanvasTiledPixmap(it->second, it->first.size(),
249 QPoint(), true, m_canvas_border);
250 t->moveTo(it->first.topLeft());
251 t->show();
254 else
255 kError() << "Unexpected type in boost::variant";
258 void ChessBoard::createGrid(Point p, const QStringList& border_coords) {
259 m_border_coords = border_coords;
260 m_sprites = PieceGrid(p.x,p.y);
261 recreateBorder();
264 QPixmap ChessBoard::loadSprite(const QString& id) {
265 return m_loader.piecePixmap(id, m_flipped);
268 QRect ChessBoard::computeRect(Point p) const {
269 QPoint realPoint = converter()->toReal(p);
270 return squareRect(realPoint.x(), realPoint.y());
273 QRect ChessBoard::squareRect(int x, int y) const {
274 return QRect(x, y, m_square_size, m_square_size);
277 QRegion ChessBoard::computeRegion(Point p) const {
278 return QRegion(computeRect(p));
281 // selection
282 void ChessBoard::setSelection(const Point& p) {
283 lastSelection = selection;
284 selection = p;
285 setTags("selection", p);
288 void ChessBoard::cancelSelection() {
289 lastSelection = selection;
290 selection = Point::invalid();
291 clearTags("selection");
294 // premove
296 void ChessBoard::setPremove(const Move& premove) {
297 m_premove_from = premove.src();
298 m_premove_to = premove.dst();
299 setTags("premove", m_premove_from, m_premove_to);
302 void ChessBoard::cancelPremove() {
303 m_premove_from = Point::invalid();
304 m_premove_to = Point::invalid();
305 clearTags("premove");
308 void ChessBoard::updateSprites() {
309 // adjust piece positions
310 for (Point i = m_sprites.first(); i <= m_sprites.last(); i = m_sprites.next(i)) {
311 boost::shared_ptr<Sprite> p = m_sprites[i].sprite();
313 if (p) {
314 // drawing sprite
315 p->setPixmap(loadSprite(m_sprites[i].name()));
316 adjustSprite(i, true);
321 void ChessBoard::updateTags() {
322 if(!m_square_size)
323 return;
325 for(BoardTags::iterator tit = m_tags->begin(); tit != m_tags->end(); ++tit)
326 for(std::map<Point, boost::shared_ptr<KGameCanvasPixmap> >::iterator pt =
327 tit->second.begin(); pt != tit->second.end(); ++pt) {
328 pt->second->moveTo(converter()->toReal(pt->first));
329 pt->second->setPixmap(m_tags_loader.getPixmap(tit->first));
334 bool ChessBoard::doMove(Move& m) {
335 if (m_entity.lock()->oneClickMoves() || m_entity.lock()->validTurn(m.src()) == Moving) {
336 if (m_entity.lock()->testMove(m)) {
337 m_entity.lock()->executeMove(m);
338 return true;
342 kDebug() << "invalid move";
343 error(InvalidMove);
345 return false;
349 void ChessBoard::onResize(int new_size, int border_size, int border_text_near,
350 int border_text_far, bool force_reload) {
351 if(m_square_size == new_size && !force_reload)
352 return;
354 m_square_size = new_size;
355 m_border_size = border_size;
356 m_border_text_near = border_text_near;
357 m_border_text_far = border_text_far;
359 // update the size of the piece loader
360 m_loader.setSize(m_square_size);
362 // update the size of the tag loader
363 m_tags_loader.setSize(m_square_size);
365 // update the size of the controls loader
366 m_controls_loader.setSize(m_border_size);
368 // update canvas background
369 updateBackground();
371 // update the sprites
372 updateSprites();
374 // update piece tags
375 updateTags();
377 // update border
378 updateBorder();
381 void ChessBoard::onMousePress(const QPoint& pos, int button) {
382 Point point = converter()->toLogical(pos);
383 if (!m_sprites.valid(point))
384 return;
386 //BEGIN left click
388 if (button == Qt::LeftButton) {
389 if (m_entity.lock()->oneClickMoves()) {
390 Move m(Point::invalid(), point);
391 doMove(m);
393 else {
394 shared_ptr<Sprite> piece = m_sprites[point].sprite();
396 if (piece && m_entity.lock()->movable(point)) {
397 cancelSelection();
398 m_drag_info = DragInfoPtr(new DragInfo(point, pos, piece,
399 m_entity.lock()->validTurn(point)) );
400 piece->raise();
403 // if selection is valid, (pre)move to point
404 else if (selection != Point::invalid()) {
405 piece = m_sprites[selection].sprite();
406 Q_ASSERT(piece);
407 Move m(selection, point);
409 switch(m_entity.lock()->validTurn(selection)) {
411 case Moving:
412 doMove(m);
413 cancelSelection();
414 break;
416 case Premoving:
417 if (m_entity.lock()->testPremove(m)) {
418 m_entity.lock()->addPremove(m);
419 setPremove(m);
420 cancelSelection();
422 break;
424 default:
425 break;
431 //END left click
433 //BEGIN right click
435 else if (button == Qt::RightButton) {
436 cancelSelection();
437 if (point == m_premove_from || point == m_premove_to)
438 cancelPremove();
439 m_entity.lock()->handleRightClick(point);
442 //END right click
445 void ChessBoard::onMouseRelease(const QPoint& pos, int button) {
446 Point point = converter()->toLogical(pos);
448 //BEGIN left click
450 if (button == Qt::LeftButton) {
452 if (m_drag_info) {
453 // Q_ASSERT(m_drag_info->piece);
454 Q_ASSERT(m_drag_info->sprite);
455 bool moved = false;
457 // remove valid move highlighting
458 clearTags("validmove");
460 // toggle selection if the piece didn't move
461 if (m_drag_info->from == point) {
462 if (lastSelection == point)
463 cancelSelection();
464 else
465 setSelection(point);
468 else {
469 Point dst = point;
470 if (!m_sprites.valid(point))
471 dst = Point::invalid();
472 Move m(m_drag_info->from, dst);
474 switch (m_entity.lock()->validTurn(m.src())) {
475 case Moving:
476 if (doMove(m))
477 moved = true;
478 break;
480 case Premoving:
481 if (m_entity.lock()->testPremove(m)) {
482 m_entity.lock()->addPremove(m);
483 setPremove(m);
485 break;
487 default:
488 break;
492 shared_ptr<Sprite> s = m_sprites[m_drag_info->from].sprite();
493 if (!moved && s && s->pos() != converter()->toReal(m_drag_info->from)) {
494 Q_ASSERT(s);
495 QPoint real = converter()->toReal(m_drag_info->from);
496 if( (point == m_drag_info->from) ? 0/* !m_anim_movement*/ : 0 /* !m_anim_fade*/) //BROKEN
497 m_anim_manager->enqueue(shared_ptr<Animation>(new InstantAnimation(s, real)));
498 else if (point == m_drag_info->from)
499 m_anim_manager->enqueue(shared_ptr<Animation>(new MovementAnimation(s, real)));
500 else
501 m_anim_manager->enqueue(shared_ptr<Animation>(new TeleportAnimation(s, s->pos(), real)));
504 m_drag_info = DragInfoPtr();
508 //END left button
511 void ChessBoard::onMouseMove(const QPoint& pos, int /*button*/) {
512 Point point = converter()->toLogical(pos);
513 static Point cachedPoint = Point();
514 static bool cachedValid;
516 if (m_drag_info) {
517 Q_ASSERT(m_drag_info->sprite);
518 // check drag threshold
519 if (!m_drag_info->dragStarted) {
520 QPoint delta = pos - m_drag_info->real;
521 if (delta.x() * delta.x() + delta.y() * delta.y() > DragInfo::DRAG_THRESHOLD) {
522 m_drag_info->dragStarted = true;
525 if (m_drag_info->dragStarted)
526 m_drag_info->sprite->moveTo(pos - QPoint(m_square_size / 2, m_square_size / 2) );
528 // highlight valid moves
529 Move move(m_drag_info->from, point);
530 bool valid = m_sprites.valid(point);
531 if (valid) {
532 InteractionType action = m_entity.lock()->validTurn(move.src());
533 if (action == Moving)
534 if (point == cachedPoint)
535 valid = cachedValid;
536 else {
537 valid = m_entity.lock()->testMove(move);
538 cachedPoint = point;
539 cachedValid = valid;
543 if (valid)
544 setTags("validmove", point);
545 else
546 clearTags("validmove");
548 else if (m_entity.lock()->oneClickMoves()) {
549 if(point == m_hinting_pos)
550 return;
552 Piece hint;
554 if (m_sprites.valid(point)) {
555 Move move(Point::invalid(), point);
556 if (m_entity.lock()->testMove(move)) {
557 // set move hint
558 hint = m_entity.lock()->moveHint(move);
562 updateHinting(point, hint);
566 void ChessBoard::onPositionChanged() {
567 if (m_entity.lock() && m_entity.lock()->oneClickMoves() && m_sprites.valid(m_hinting_pos)) {
568 Piece hint;
570 Move move(Point::invalid(), m_hinting_pos);
571 if (m_entity.lock()->testMove(move)) {
572 // set move hint
573 hint = m_entity.lock()->moveHint(move);
576 updateHinting(move.dst(), hint);
580 void ChessBoard::onMouseLeave() {
581 updateHinting(Point::invalid(), Piece());
584 void ChessBoard::updateHinting(Point pt, Piece piece) {
585 if(!m_sprites.valid(pt))
586 piece = Piece();
589 QString piece_name = m_namer->name(piece);
590 if(piece != Piece() || !m_sprites.valid(pt)) {
591 if(m_hinting.sprite()) {
592 if(1 /*BROKEN m_anim_fade*/)
593 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
594 m_hinting.sprite()->pos(), 160, 0)) );
595 else
596 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new CaptureAnimation(m_hinting.sprite())) );
599 m_hinting_pos = Point::invalid();
600 m_hinting = NamedSprite();
602 else {
603 if(pt == m_hinting_pos) {
604 if (piece_name != m_hinting.name()) {
605 m_hinting = NamedSprite(piece_name, m_hinting.sprite());
606 m_hinting.sprite()->setPixmap(loadSprite(piece_name));
609 else {
610 if(m_hinting.sprite()) {
611 if(1 /*BROKEN m_anim_fade*/)
612 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
613 m_hinting.sprite()->pos(), 160, 0)) );
614 else
615 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new CaptureAnimation(m_hinting.sprite())) );
618 QPixmap pix = loadSprite(piece_name);
619 SpritePtr sprite(new Sprite(pix, piecesGroup(), converter()->toReal(pt)));
620 sprite->setOpacity(160);
621 sprite->raise();
622 sprite->show();
624 m_hinting_pos = pt;
625 m_hinting = NamedSprite(piece_name, sprite);
627 /*if(m_anim_fade)
628 m_anim_manager->enqueue( boost::shared_ptr<Animation>(new FadeAnimation(m_hinting.sprite(),
629 m_hinting.sprite()->pos(), 0, 160)) );
630 else {
631 m_hinting.sprite()->setOpacity(160);
632 m_anim_manager->enqueue(boost::shared_ptr<Animation>(new DropAnimation(m_hinting.sprite())) );
638 void ChessBoard::reset() {
639 clearTags();
640 cancelSelection();
641 cancelPremove();
642 m_anim_manager->stop();
645 void ChessBoard::flip(bool flipped)
647 if (m_flipped != flipped) {
648 m_flipped = flipped;
650 // update sprite positions
651 for (Point i = m_sprites.first(); i <= m_sprites.last(); i = m_sprites.next(i)) {
652 SpritePtr p = m_sprites[i].sprite();
653 if (p) {
654 p->setPixmap(loadSprite(m_sprites[i].name()));
655 adjustSprite(i, true);
659 updateTags();
660 updateBorder();
664 void ChessBoard::draggingOn(const IColor* pool, int index, const QPoint& point) {
665 Point to = converter()->toLogical(point);
667 if (m_sprites.valid(to))
668 switch (m_entity.lock()->validTurn(pool)) {
669 case Moving: {
670 Move m(pool, index, to);
671 if (m_entity.lock()->testMove(m)) {
672 setTags("validmove", to);
673 return;
675 break;
678 case Premoving:
679 setTags("validmove", to);
680 return;
682 default:
683 break;
686 clearTags("validmove");
689 bool ChessBoard::dropOn(const IColor* pool, int index, const QPoint& point) {
691 Point to = converter()->toLogical(point);
692 if (!m_sprites.valid(to))
693 return false;
695 clearTags("validmove");
697 switch (m_entity.lock()->validTurn(pool)) {
699 case Moving: {
700 Move m(pool, index, to);
701 if (m_entity.lock()->testMove(m)) {
702 m_entity.lock()->executeMove(m);
703 return true;
705 break;
708 case Premoving: {
709 Move m(pool, index, to);
710 if (m_entity.lock()->testPremove(m)) {
711 m_entity.lock()->addPremove(m);
712 setPremove(m);
714 break;
717 default:
718 break;
721 kDebug() << "invalid move";
722 error(InvalidMove);
723 return false;