1 /**********************************************************************
2 *
3 * FreeDoko a Doppelkopf-Game
4 *
5 * Copyright (C) 2001 – 2018 by Diether Knof and Borg Enders
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 * You can find this license in the file 'gpl.txt'.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
21 * MA 02111-1307 USA
22 *
23 * Contact:
24 * Diether Knof dknof@posteo.de
25 *
26 **********************************************************************/
27
28 #include "constants.h"
29
30 #ifdef USE_UI_GTKMM
31
32 #include "poverty.h"
33
34 #include "table.h"
35 #include "hand.h"
36 #include "icongroup.h"
37 #include "name.h"
38 #include "ui.h"
39 #include "cards.h"
40
41 #include "../../game/game.h"
42 #include "../../party/rule.h"
43 #include "../../player/player.h"
44 #include "../../card/hand.h"
45 #include "../../os/bug_report_replay.h"
46
47 #include <gtkmm/main.h>
48 #include <gdkmm/general.h>
49
50 namespace Gdk {
51 ostream& operator<<(ostream& ostr, Rectangle const& rectangle);
52 ostream& operator<<(ostream& ostr, Point const& point);
53 } // namespace Gdk
54
55 namespace UI_GTKMM_NS {
56 /** constructor
57 **
58 ** @param table parent object
59 **/
Poverty(Table & table)60 Poverty::Poverty(Table& table) :
61 Table::Part(table)
62 { }
63
64 /** Destructor
65 **/
66 Poverty::~Poverty() = default;
67
68 /** @return the position of the poverty
69 **/
70 Position
position() const71 Poverty::position() const
72 {
73 if (!this->player)
74 return Position::center;
75 return this->table().position(*this->player);
76 } // Position Poverty::position() const
77
78 /** @return the corresponding hand
79 **/
80 Hand const&
hand() const81 Poverty::hand() const
82 {
83 return this->table().hand(this->position());
84 } // Hand Poverty::hand() const
85
86 /** @return the corresponding hand
87 **/
88 Hand&
hand()89 Poverty::hand()
90 {
91 return this->table().hand(this->position());
92 } // Hand& Poverty::hand()
93
94 /** @return the corresponding icongroup
95 **/
96 Icongroup const&
icongroup() const97 Poverty::icongroup() const
98 {
99 return this->table().icongroup(this->position());
100 } // Icongroup Poverty::icongroup() const
101
102 /** @return the corresponding name
103 **/
104 Name const&
name() const105 Poverty::name() const
106 {
107 return this->table().name(this->position());
108 } // Icongroup Poverty::name() const
109
110 /** @return whether the active player can shift
111 **/
112 bool
middle_full() const113 Poverty::middle_full() const
114 {
115 return !contains(this->cards, Card());
116 } // bool Poverty::middle_full() const
117
118 /** @return whether the active player can shift
119 **/
120 bool
shifting_valid() const121 Poverty::shifting_valid() const
122 {
123 if (this->player->type() != Player::Type::human)
124 return false;
125
126 switch (this->status) {
127 case Status::invalid:
128 return false;
129 case Status::asking:
130 return true;
131 case Status::shifting:
132 case Status::shifting_back:
133 return this->middle_full();
134 case Status::getting_back:
135 return false;
136 case Status::accepted:
137 case Status::denied_by_all:
138 case Status::finished:
139 return false;
140 } // switch (this->status)
141
142 return false;
143 } // bool Poverty::shifting_valid() const
144
145 /** 'player' shifts 'cardno' cards
146 **
147 ** @param player the player who shifts the cards
148 ** @param cardno the number of cards that are shifted
149 **/
150 void
shift(Player const & player,unsigned const cardno)151 Poverty::shift(Player const& player, unsigned const cardno)
152 {
153 this->status = Status::shifting;
154
155 if ( (player.type() != Player::Type::human)
156 //&& (player.game().humanno() > 0)
157 && !(::fast_play & FastPlay::pause)
158 && !(::bug_report_replay && ::bug_report_replay->auto_action()) ) {
159 this->shift_cards = false;
160 this->cards = player.game().poverty().shifted_cards();
161 this->player = &player;
162
163 this->table().draw_all();
164
165 while (!this->ui->thrower
166 && (::game_status == GameStatus::game_poverty_shift)
167 && (this->shift_cards == false))
168 ::ui->wait();
169 if (this->ui->thrower)
170 return ;
171 } // if (player.type() != Player::Type::human)
172
173 this->status = Status::asking;
174 } // void Poverty::shift(Player player, unsigned cardno)
175
176 /** 'player' denied to take the shifted cards
177 **
178 ** @param player the player who has denied to take the cards
179 **/
180 void
take_denied(Player const & player)181 Poverty::take_denied(Player const& player)
182 { }
183
184 /** all players have denied to take the cards
185 **/
186 void
take_denied_by_all()187 Poverty::take_denied_by_all()
188 {
189 this->player = &this->player->game().players().soloplayer();
190 this->status = Status::denied_by_all;
191 } // void Poverty::take_denied_by_all()
192
193 /** 'player' accepts to take the shifted cards
194 ** and returns 'cardno' cards with 'trumpno' trumps
195 ** show the cards and wait for a click
196 **
197 ** @param player the player who has denied to take the cards
198 ** @param cardno number of cards that are given back
199 ** @param trumpno number of trumps of the cards
200 **/
201 void
take_accepted(Player const & player,unsigned const cardno,unsigned const trumpno)202 Poverty::take_accepted(Player const& player,
203 unsigned const cardno, unsigned const trumpno)
204 {
205 this->status = Status::accepted;
206 this->force_redraw();
207 this->table().draw_all();
208 } // void Poverty::take_accepted(Player player, unsigned cardno, unsigned trumpno)
209
210 /** returns which cards the player shifts
211 **
212 ** @param player the player who shifts the cards (the soloplayer)
213 **
214 ** @return the cards that are to be shifted
215 **/
216 HandCards
shift(Player & player)217 Poverty::shift(Player& player)
218 {
219 this->status = Status::shifting;
220
221 this->cards = HandCards(player.hand().count_poverty_cards());
222 this->player = &player;
223 auto hand = player.hand().sorted();
224
225 this->shift_cards = false;
226
227 // put all trump in the middle
228 unsigned j = 0;
229 for (unsigned i = 0; i < hand.cardsnumber_all(); i++)
230 if (hand.card_all(i).istrump()) {
231 this->cards[j] = hand.card_all(i);
232 hand.playcard(i - j);
233 j += 1;
234 }
235
236 this->table().draw_all();
237
238 while (!this->ui->thrower
239 && (::game_status == GameStatus::game_poverty_shift)
240 && (this->shift_cards == false))
241 ::ui->wait();
242 if (this->ui->thrower)
243 return {};
244
245 if (::game_status != GameStatus::game_poverty_shift)
246 return this->cards;
247
248 hand.remove(this->cards);
249
250 return this->cards;
251 } // HandCards Poverty::shift(Player& player)
252
253 /** ask 'player' whether to accept the poverty
254 **
255 ** @param player the player who is asked
256 ** @param cardno the number of shifted cards
257 **/
258 void
ask(Player const & player,unsigned const cardno)259 Poverty::ask(Player const& player, unsigned const cardno)
260 {
261 //this->cards = HandCards(cardno);
262 this->cards = player.game().poverty().shifted_cards();
263 this->player = &player;
264
265 this->table().draw_all();
266 } // void Poverty::ask(Player player, unsigned cardno)
267
268 /** returns whether 'player' accepts the shifted cards
269 **
270 ** @param player the player who shifts the cards
271 ** @param cardno the number of shifted cards
272 **
273 ** @return whether to accept the cards
274 **/
275 bool
take_accept(Player const & player,unsigned const cardno)276 Poverty::take_accept(Player const& player, unsigned const cardno)
277 {
278 this->status = Status::asking;
279
280 //this->cards = HandCards(cardno);
281 this->cards = player.game().poverty().shifted_cards();
282 this->player = &player;
283
284 this->accept_cards = false;
285 this->shift_cards = false;
286
287 this->table().draw_all();
288
289 while (!this->ui->thrower
290 && (::game_status == GameStatus::game_poverty_shift)
291 && (this->accept_cards == false)
292 && (this->shift_cards == false))
293 ::ui->wait();
294 if (this->ui->thrower)
295 return false;
296
297 return this->accept_cards;
298 } // bool Poverty::take_accept(Player const& player, unsigned cardno)
299
300 /** changes the cards from the poverty-player
301 **
302 ** @param player the player who has accepted the cards
303 ** @param cards_shifted the cards that are given to the player
304 **
305 ** @return the cards that are returned to the poverty-player
306 **/
307 HandCards
cards_change(Player & player,HandCards const & cards_shifted)308 Poverty::cards_change(Player& player, HandCards const& cards_shifted)
309 {
310 this->status = Status::shifting_back;
311
312 this->player = &player;
313 auto hand = player.hand().sorted();
314
315 this->accept_cards = false;
316 this->shift_cards = false;
317
318 // add the cards to the hand, but mark all cards in the middle as played
319 hand.add(cards_shifted);
320 for (auto const& c : cards_shifted)
321 player.hand().playcard(c);
322 this->cards.clear();
323 for (unsigned c = 0; c < hand.cardsnumber_all(); ++c)
324 if (hand.played(c))
325 this->cards.push_back(hand.card_all(c));
326
327 this->table().draw_all();
328
329 while (!this->ui->thrower
330 && (::game_status == GameStatus::game_poverty_shift)
331 && (this->shift_cards == false))
332 ::ui->wait();
333 if (this->ui->thrower)
334 return HandCards();
335
336 // remove the cards to shift from the hand of the player
337 hand.remove(this->cards);
338
339 return this->cards;
340 } // HandCards Poverty::cards_change(Player& player, HandCards cards_shifted)
341
342 /** changes the cards from the poverty-player
343 **
344 ** @param player the player who has accepted the cards
345 ** @param cards_returned the cards that are given to the player
346 **
347 ** @return the cards that are returned to the poverty-player
348 **/
349 void
cards_get_back(Player const & player_,HandCards const & cards_returned)350 Poverty::cards_get_back(Player const& player_,
351 HandCards const& cards_returned)
352 {
353 Player& player = this->ui->game().player(player_.no());
354 auto hand = player.hand().sorted();
355 if (player.type() != Player::Type::human)
356 return ;
357
358 this->status = Status::getting_back;
359
360 this->player = &player;
361
362 this->accept_cards = false;
363
364 // add the cards to the hand, but mark all cards in the middle as played
365 hand.add(cards_returned);
366 for (auto const& c : cards_returned)
367 player.hand().playcard(c);
368 this->cards.clear();
369 for (unsigned c = 0; c < hand.cardsnumber_all(); ++c)
370 if (hand.played(c))
371 this->cards.push_back(hand.card_all(c));
372
373 this->table().draw_all();
374
375 while (!this->ui->thrower
376 && (::game_status == GameStatus::game_poverty_shift)
377 && (this->accept_cards == false))
378 ::ui->wait();
379 if (this->ui->thrower)
380 return ;
381
382 for (auto const& c : this->cards)
383 hand.unplaycard(c);
384
385 this->status = Status::finished;
386 } // void Poverty::cards_get_back(Player& player, HandCards cards_returned)
387
388 /** -> result
389 **
390 ** @param cardno number of card
391 **
392 ** @return position of the cards (relative)
393 **/
394 vector<Gdk::Point>
cards_pos() const395 Poverty::cards_pos() const
396 {
397 if (!this->player)
398 return {};
399 int const card_width = this->ui->cards->width();
400 int const card_height = this->ui->cards->height();
401 int const width = this->geometry_cards().width();
402 int const height = this->geometry_cards().height();
403 int const max_pos_y = -max(0, height - card_height);
404 int const cardsnumber = this->cards.size();
405
406 auto const gap = min(card_width / 2,
407 (width - cardsnumber * card_width)
408 / max(1, cardsnumber - 1));
409 switch (cardsnumber) {
410 case 0:
411 case 1:
412 return {{-card_width / 2, 0}};
413 case 2:
414 return {{-card_width - gap / 2, 0},
415 {gap / 2, 0},
416 };
417 case 3:
418 return {{-card_width - gap - card_width / 2, 0},
419 {-card_width / 2, max(-card_height / 4, max_pos_y)},
420 {card_width / 2 + gap, 0},
421 };
422 case 4:
423 return {{-card_width - gap - card_width - gap / 2, 0},
424 {-card_width - gap / 2, max(-card_height / 4, max_pos_y)},
425 {gap / 2, max(-card_height / 4, max_pos_y)},
426 {gap / 2 + card_width + gap, 0},
427 };
428 case 5:
429 return {{-card_width - gap - card_width - gap - card_width / 2, 0},
430 {-card_width - gap - card_width / 2, max(-card_height / 4, max_pos_y)},
431 {-card_width / 2, max(-card_height / 3, max_pos_y)},
432 {card_width / 2 + gap, max(-card_height / 4, max_pos_y)},
433 {card_width / 2 + gap + card_width + gap, 0},
434 };
435 }; // switch (cardsnumber)
436 return {};
437 } // vector<Gdk::Point> Poverty::cards_pos() const
438
439 /** @return whether the drawing has changed
440 **/
441 bool
changed() const442 Poverty::changed() const
443 {
444 return true;
445 } // bool Poverty::changed() const
446
447 /** draws the poverty elements in the table
448 **
449 ** @param cr cairo context
450 **/
451 void
draw(Cairo::RefPtr<::Cairo::Context> cr)452 Poverty::draw(Cairo::RefPtr<::Cairo::Context> cr)
453 {
454 if (!this->is_drawn())
455 return ;
456
457 this->draw_arrow(cr);
458 this->draw_cards(cr);
459 } // void Poverty::draw(Cairo::RefPtr<::Cairo::Context> cr)
460
461 /** Draw the cards
462 **
463 ** @param cr graphic context
464 **/
465 void
draw_cards(Cairo::RefPtr<::Cairo::Context> cr)466 Poverty::draw_cards(Cairo::RefPtr<::Cairo::Context> cr)
467 {
468 auto const cardno = this->cards.size();
469 auto const positions = this->cards_pos();
470
471 cr->push_group();
472 this->geometry_cards().transform(cr);
473 cr->translate(0, -this->ui->cards->height());
474
475 if (cardno == 0) {
476 auto card = this->ui->cards->back();
477 draw_pixbuf(cr, card, positions[0]);
478 } else { // if !(cardno == 0)
479 DEBUG_ASSERTION(cardno == positions.size(),
480 "Poverty::draw()\n"
481 " cardno = " << cardno << " != " << positions.size() << " = position.size()");
482 for (unsigned i = 0; i < cardno; i++) {
483 auto card = this->ui->cards->card(Card());
484 if ( ( (this->status != Status::asking)
485 && !( (this->status == Status::shifting)
486 && (this->player->type() != Player::Type::human)) )
487 || ::preferences(::Preferences::Type::show_all_hands)
488 || ( this->game().rule()(Rule::Type::poverty_fox_shift_open)
489 #ifdef WORKAROUND
490 // Bug:
491 // 'isfox' needs a corresponding hand,
492 // but 'cards' does not belong anymore to the hand of
493 // the poverty player.
494 // The only problem exists, if the card is a swine, but
495 // then the swines are already announced
496 && this->cards[i].istrumpace()
497 && !this->game().swines().swines_announced()
498 #else
499 && this->cards[i].isfox()
500 #endif
501 ) )
502 card = this->ui->cards->card(this->cards[i]);
503 draw_pixbuf(cr, card, positions[i]);
504 } // for (i < cardno)
505 } // if !(cardno == 0)
506 cr->pop_group_to_source();
507 cr->paint();
508 } // void Poverty::draw_cards(Cairo::RefPtr<::Cairo::Context> cr)
509
510 /** Draw the arrow
511 **
512 ** @param cr graphic context
513 **/
514 void
draw_arrow(Cairo::RefPtr<::Cairo::Context> cr)515 Poverty::draw_arrow(Cairo::RefPtr<::Cairo::Context> cr)
516 {
517 if (this->player->type() != Player::Type::human)
518 return ;
519 if (!this->shifting_valid())
520 return ;
521
522 cr->save();
523 auto const geometry = this->geometry_arrow();
524 geometry.transform(cr);
525
526 bool const mirror = (this->status == Status::shifting_back);
527
528 int const width = geometry.width();
529 int const height = geometry.height();
530
531 cr->scale(width, height / 2);
532 if (mirror) {
533 cr->scale(-1, 1);
534 }
535 cr->translate(-0.5, 0);
536 cr->move_to(0, -1);
537 cr->scale(1, 1 / 1.25);
538 cr->rel_line_to(1.0 / 6, -1.25); // Obere Ecke
539 cr->rel_line_to(0, 0.75);
540 cr->rel_line_to(5.0 / 6, 0);
541 cr->rel_line_to(0, 1);
542 cr->rel_line_to(-5.0 / 6, 0);
543 cr->rel_line_to(0, 0.75);
544 cr->close_path();
545 cr->set_source_rgb(0, 0, 0);
546 cr->fill();
547 cr->restore();
548 } // void Poverty::draw_arrow(Cairo::RefPtr<::Cairo::Context> cr)
549
550 /** @return the outline of the trick
551 **/
552 Poverty::Outline
outline() const553 Poverty::outline() const
554 {
555 if (!this->is_drawn())
556 return {0, 0, 1, 1};
557
558 return (this->geometry_arrow().outline()
559 + this->geometry_cards().outline());
560 } // Outline Poverty::outline() const
561
562 /** @return the geometry for the arrow
563 **/
564 Poverty::GeometryArrow
geometry_arrow() const565 Poverty::geometry_arrow() const
566 {
567 GeometryArrow geometry;
568 auto const hand_geometry = this->hand().geometry();
569 auto const icongroup_geometry = this->icongroup().geometry();
570 auto table = &this->table();
571 geometry.width = [=](){
572 return min(hand_geometry.width() * 3 / 4,
573 max(2 * this->ui->cards->width(),
574 hand_geometry.width() * 1 / 2));
575 };
576 geometry.height = [=](){
577 return this->ui->cards->height() * 2 / 10;
578 };
579 switch (this->table().layout()) {
580 case Table::Layout::standard:
581 switch (this->position()) {
582 case Position::south:
583 geometry.rotation = Rotation::up;
584 geometry.pos_x = [=](){
585 return (hand_geometry.pos_x()
586 + hand_geometry.width() / 2);
587 };
588 geometry.pos_y = [=](){
589 return (hand_geometry.pos_y()
590 - hand_geometry.height()
591 - hand_geometry.margin_y()
592 - icongroup_geometry.height()
593 - 2 * hand_geometry.margin_y());
594 };
595 break;
596
597 case Position::north:
598 geometry.rotation = Rotation::down;
599 geometry.pos_x = [=](){
600 return (hand_geometry.pos_x()
601 - hand_geometry.width() / 2);
602 };
603 geometry.pos_y = [=](){
604 return (hand_geometry.pos_y()
605 + hand_geometry.height()
606 + hand_geometry.margin_y()
607 + icongroup_geometry.height()
608 + 2 * hand_geometry.margin_y());
609 };
610 break;
611
612 case Position::west:
613 geometry.rotation = Rotation::right;
614 geometry.pos_x = [=](){
615 return (hand_geometry.pos_x()
616 + hand_geometry.height()
617 + hand_geometry.margin_y()
618 + icongroup_geometry.height()
619 + 2 * hand_geometry.margin_y());
620 };
621 geometry.pos_y = [=](){
622 return (hand_geometry.pos_y()
623 + hand_geometry.width() / 2);
624 };
625 break;
626
627 case Position::east:
628 geometry.rotation = Rotation::left;
629 geometry.pos_x = [=](){
630 return (hand_geometry.pos_x()
631 - hand_geometry.height()
632 - hand_geometry.margin_y()
633 - icongroup_geometry.height()
634 - 2 * hand_geometry.margin_y());
635 };
636 geometry.pos_y = [=](){
637 return (hand_geometry.pos_y()
638 - hand_geometry.width() / 2);
639 };
640 break;
641
642 case Position::center:
643 DEBUG_ASSERTION(false,
644 "Poverty::geometry()\n"
645 " wrong position 'center' "
646 << "(" << this->position() << ")");
647 } // switch (this->position())
648 break;
649
650 case Table::Layout::widescreen:
651 switch (this->position()) {
652 case Position::south:
653 geometry.rotation = Rotation::up;
654 geometry.pos_x = [=](){
655 return (hand_geometry.pos_x()
656 + hand_geometry.width() / 2);
657 };
658 geometry.pos_y = [=](){
659 return (hand_geometry.pos_y()
660 - hand_geometry.height()
661 - hand_geometry.margin_y()
662 - icongroup_geometry.height()
663 - 2 * hand_geometry.margin_y());
664 };
665 break;
666
667 case Position::north: {
668 geometry.rotation = Rotation::down;
669 auto const hand_geometry = this->hand().geometry();
670 auto const hand_south_geometry = table->hand(Position::south).geometry();
671 geometry.pos_x = [=](){
672 return (hand_south_geometry.pos_x()
673 + hand_south_geometry.width() / 2);
674 };
675 geometry.pos_y = [=](){
676 return (hand_geometry.pos_y()
677 + hand_geometry.height()
678 + hand_geometry.margin_y());
679 };
680 break;
681 }
682
683 case Position::west: {
684 geometry.rotation = Rotation::right;
685 auto const hand_south_geometry = table->hand(Position::south).geometry();
686 auto const icongroup_south_geometry = table->icongroup(Position::south).geometry();
687 auto const hand_north_geometry = table->hand(Position::north).geometry();
688 auto const icongroup_north_geometry = table->icongroup(Position::north).geometry();
689 geometry.pos_x = [=](){
690 return hand_south_geometry.pos_x();
691 };
692 geometry.pos_y = [=](){
693 auto const y = (icongroup_north_geometry.pos_y()
694 + icongroup_north_geometry.height()
695 + hand_south_geometry.margin_y());
696 return (y
697 + (icongroup_south_geometry.pos_y()
698 - icongroup_south_geometry.height()
699 - hand_south_geometry.margin_y()
700 - y
701 ) / 2);
702 };
703 geometry.width = [=](){
704 auto const y = (icongroup_north_geometry.pos_y()
705 + icongroup_north_geometry.height()
706 + hand_north_geometry.margin_y()
707 + hand_south_geometry.margin_y());
708 return min(this->geometry_cards().width() * 2 / 3,
709 icongroup_south_geometry.pos_y()
710 - icongroup_south_geometry.height()
711 - hand_south_geometry.margin_y()
712 - y
713 );
714 };
715 break;
716 }
717
718 case Position::east:
719 geometry.rotation = Rotation::left;
720 geometry.pos_x = [=](){
721 return (hand_geometry.pos_x()
722 - hand_geometry.height()
723 - hand_geometry.margin_y()
724 - icongroup_geometry.height()
725 - 2 * hand_geometry.margin_y());
726 };
727 geometry.pos_y = [=](){
728 return (hand_geometry.pos_y()
729 - hand_geometry.width() / 2);
730 };
731 break;
732
733 case Position::center:
734 DEBUG_ASSERTION(false,
735 "Poverty::geometry()\n"
736 " wrong position 'center' "
737 << "(" << this->position() << ")");
738 } // switch (this->position())
739 break;
740 } // switch (this->table().layout())
741 return geometry;
742 } // GeometryArrow Poverty::geometry_arrow() const
743
744 /** @return the outline of the arrow
745 **/
746 Table::Part::Outline
outline() const747 Poverty::GeometryArrow::outline() const
748 {
749 return this->transform({-this->width() / 2, -this->height(),
750 this->width(), this->height()});
751 } // Table::Part::Outline Poverty::GeometryArrow::outline() const
752
753 /** @return the geometry for the cards
754 **/
755 Poverty::GeometryCards
geometry_cards() const756 Poverty::geometry_cards() const
757 {
758 GeometryCards geometry;
759 auto const geometry_arrow = this->geometry_arrow();
760 auto const hand_geometry = this->hand().geometry();
761 auto table = &this->table();
762 geometry.width = [=](){
763 return hand_geometry.width() * 3 / 4;
764 };
765 switch (this->table().layout()) {
766 case Table::Layout::standard:
767 switch (this->position()) {
768 case Position::south:
769 geometry.rotation = Rotation::up;
770 geometry.pos_x = [=](){
771 return (hand_geometry.pos_x()
772 + hand_geometry.width() / 2);
773 };
774 geometry.pos_y = [=](){
775 return (geometry_arrow.pos_y()
776 - geometry_arrow.height()
777 - 2 * hand_geometry.margin_y());
778 };
779 geometry.height = [=](){
780 return min(this->ui->cards->height() * 3 / 2,
781 geometry.pos_y()
782 - table->icongroup(Position::north).geometry().pos_y()
783 - table->icongroup(Position::north).geometry().height()
784 - table->hand(Position::north).geometry().margin_y()
785 );
786 };
787 break;
788
789 case Position::north:
790 geometry.rotation = Rotation::down;
791 geometry.pos_x = [=](){
792 return (hand_geometry.pos_x()
793 - hand_geometry.width() / 2);
794 };
795 geometry.pos_y = [=](){
796 return (geometry_arrow.pos_y()
797 + geometry_arrow.height()
798 + 2 * hand_geometry.margin_y());
799 };
800 geometry.height = [=](){
801 return min(this->ui->cards->height() * 3 / 2,
802 table->icongroup(Position::south).geometry().pos_y()
803 - table->icongroup(Position::south).geometry().height()
804 - table->hand(Position::south).geometry().margin_y()
805 - geometry.pos_y()
806 );
807 };
808 break;
809
810 case Position::west:
811 geometry.rotation = Rotation::right;
812 geometry.pos_x = [=](){
813 return (geometry_arrow.pos_x()
814 + geometry_arrow.height()
815 + 2 * hand_geometry.margin_y());
816 };
817 geometry.pos_y = [=](){
818 return (hand_geometry.pos_y()
819 + hand_geometry.width() / 2);
820 };
821 geometry.height = [=](){
822 return this->ui->cards->height() * 3 / 2;
823 };
824 break;
825
826 case Position::east:
827 geometry.rotation = Rotation::left;
828 geometry.pos_x = [=](){
829 return (geometry_arrow.pos_x()
830 - geometry_arrow.height()
831 - 2 * hand_geometry.margin_y());
832 };
833 geometry.pos_y = [=](){
834 return (hand_geometry.pos_y()
835 - hand_geometry.width() / 2);
836 };
837 geometry.height = [=](){
838 return this->ui->cards->height() * 3 / 2;
839 };
840 break;
841
842 case Position::center:
843 DEBUG_ASSERTION(false,
844 "Poverty::geometry()\n"
845 " wrong position 'center' "
846 << "(" << this->position() << ")");
847 } // switch (this->position())
848 break;
849
850 case Table::Layout::widescreen:
851 switch (this->position()) {
852 case Position::south:
853 geometry.rotation = Rotation::up;
854 geometry.pos_x = [=](){
855 return geometry_arrow.pos_x();
856 };
857 geometry.pos_y = [=](){
858 return (geometry_arrow.pos_y()
859 - geometry_arrow.height()
860 - 2 * hand_geometry.margin_y());
861 };
862 geometry.height = [=](){
863 return min(this->ui->cards->height() * 3 / 2,
864 geometry.pos_y()
865 - table->border_y());
866 };
867 break;
868
869 case Position::north: {
870 geometry.rotation = Rotation::down;
871 auto const hand_south_geometry = table->hand(Position::south).geometry();
872 auto const name_geometry = this->name().geometry();
873 geometry.pos_x = [=](){
874 return geometry_arrow.pos_x();
875 };
876 geometry.pos_y = [=](){
877 return (geometry_arrow.pos_y()
878 + geometry_arrow.height()
879 + 2 * hand_south_geometry.margin_y());
880 };
881 geometry.height = [=](){
882 return min(this->ui->cards->height() * 3 / 2,
883 hand_south_geometry.pos_y()
884 - name_geometry.pos_y()
885 - name_geometry.height()
886 - hand_geometry.margin_y());
887 };
888 geometry.width = [=](){
889 return hand_south_geometry.width() * 3 / 4;
890 };
891 }
892 break;
893
894 case Position::west:
895 geometry.rotation = Rotation::right;
896 geometry.pos_x = [=](){
897 return (geometry_arrow.pos_x()
898 + geometry_arrow.height()
899 + 2 * hand_geometry.margin_y());
900 };
901 geometry.pos_y = [=](){
902 return geometry_arrow.pos_y();
903 };
904 geometry.height = [=](){
905 return this->ui->cards->height() * 3 / 2;
906 };
907 break;
908
909 case Position::east:
910 geometry.rotation = Rotation::left;
911 geometry.pos_x = [=](){
912 return (geometry_arrow.pos_x()
913 - geometry_arrow.height()
914 - 2 * hand_geometry.margin_y());
915 };
916 geometry.pos_y = [=](){
917 return geometry_arrow.pos_y();
918 };
919 geometry.height = [=](){
920 return this->ui->cards->height() * 3 / 2;
921 };
922 break;
923
924 case Position::center:
925 DEBUG_ASSERTION(false,
926 "Poverty::geometry()\n"
927 " wrong position 'center' "
928 << "(" << this->position() << ")");
929 } // switch (this->position())
930 break;
931 } // switch (this->table().layout())
932
933 return geometry;
934 } // GeometryCards Poverty::geometry_cards() const
935
936 /** @return the outline of the arrow
937 **/
938 Table::Part::Outline
outline() const939 Poverty::GeometryCards::outline() const
940 {
941 return this->transform({-this->width() / 2, -this->height(),
942 this->width(), this->height()});
943 } // Table::Part::Outline Poverty::GeometryCards::outline() const
944
945 /** if the arrow is clicked, shift the cards
946 ** if a card in the hand is clicked, move it in the cards-to-be-shifted
947 ** if a card in the middle is clicked, move it in the hand
948 ** no release: if the right button is clicked on an empty card, add cards
949 ** according to the bug report
950 **
951 ** @param button_event data of the button event
952 **
953 ** @return whether the click has been accepted
954 **/
955 bool
button_press_event(GdkEventButton * const button_event)956 Poverty::button_press_event(GdkEventButton* const button_event)
957 {
958 // whether the mouse click leads to an action
959 bool accepted = false;
960
961 switch (this->possible_action(static_cast<int>(button_event->x),
962 static_cast<int>(button_event->y))) {
963 case Action::none:
964 accepted = false;
965 break;
966 case Action::shift_cards:
967 switch (button_event->button) {
968 case 1: // left mouse button
969 this->shift_cards = true;
970 accepted = true;
971 break;
972 default:
973 break;
974 } // switch (button_event->button)
975 break;
976 case Action::accept_cards:
977 switch (button_event->button) {
978 case 1: // left mouse button
979 this->accept_cards = true;
980 accepted = true;
981 break;
982 default:
983 break;
984 } // switch (button_event->button)
985 break;
986 case Action::take_card:
987 {
988 Player& player = const_cast<Player&>(*this->player);
989 auto hand = player.hand().sorted();
990
991 // test whether a card in the middle is clicked
992 auto const cardno
993 = this->cardno_at_position(static_cast<int>(button_event->x), static_cast<int>(button_event->y));
994 DEBUG_ASSERTION(cardno,
995 "Poverty::button_press_event():\n"
996 " no card under the mouse");
997
998 // move a card in the hand
999 switch (button_event->button) {
1000 case 1: // left mouse button
1001 if (this->cards[*cardno].is_empty())
1002 break;
1003 hand.unplaycard(this->cards[*cardno]);
1004 this->cards[*cardno] = Card();
1005 this->force_redraw();
1006 this->table().draw_all();
1007 accepted = true;
1008 break;
1009 case 3: // right mouse button
1010
1011 // put all cards in the hand
1012 for (auto& c : this->cards) {
1013 if (!c.is_empty()) {
1014 if ( (this->status != Status::shifting)
1015 || (!c.istrump())) {
1016 hand.unplaycard(c);
1017 c = Card();
1018 } // if (card not trump when soloplayer)
1019 }
1020 }
1021 this->force_redraw();
1022 this->table().draw_all();
1023 accepted = true;
1024 default:
1025 break;
1026 } // switch (button_event->button)
1027 }
1028 break;
1029
1030 case Action::put_card:
1031 switch (button_event->button) {
1032 case 1: // left mouse button
1033 {
1034 auto const cardno
1035 = this->hand().cardno_at_position(static_cast<int>(button_event->x), static_cast<int>(button_event->y));
1036 DEBUG_ASSERTION(cardno,
1037 "Poverty::button_press_event():\n"
1038 " no card under the mouse");
1039 this->add_card_to_shift(*cardno);
1040 this->force_redraw();
1041 this->table().draw_all();
1042 accepted = true;
1043 }
1044 break;
1045 default:
1046 break;
1047 } // switch (button_event->button)
1048 break;
1049
1050 case Action::fill_up:
1051 // for a bug report: fill up according to the bug report
1052 switch (button_event->button) {
1053 case 3: { // right mouse button
1054 Player& player = const_cast<Player&>(*this->player);
1055 auto hand = player.hand().sorted();
1056
1057 // test whether a card in the middle is clicked
1058 auto const cardno
1059 = this->cardno_at_position(static_cast<int>(button_event->x), static_cast<int>(button_event->y));
1060 if (!cardno)
1061 break;
1062
1063 if (!( ::bug_report_replay
1064 && this->cards[*cardno].is_empty()) )
1065 break;
1066
1067 accepted = true;
1068 // fill up according to the bug report
1069 for (auto& c : this->cards) {
1070 if (!c.is_empty()) {
1071 hand.unplaycard(c);
1072 c = Card();
1073 }
1074 }
1075
1076 switch (status) {
1077 case Status::shifting:
1078 DEBUG_ASSERTION(!::bug_report_replay->poverty_cards_shifted().empty(),
1079 "UI_GTKMM:Poverty::on_mouse_click_event()\n"
1080 " the bug report does not contain shifted cards");
1081 this->cards.clear();
1082 for (auto const& c: ::bug_report_replay->poverty_cards_shifted())
1083 this->cards.push_back({player.hand(), c});
1084 break;
1085
1086 case Status::shifting_back:
1087 DEBUG_ASSERTION(!::bug_report_replay->poverty_cards_returned().empty(),
1088 "UI_GTKMM:Poverty::on_mouse_click_event()\n"
1089 " the bug report does not contain returned cards");
1090 this->cards.clear();
1091 for (auto const& c : ::bug_report_replay->poverty_cards_returned())
1092 this->cards.push_back({player.hand(), c});
1093 break;
1094 default:
1095 break;
1096 } // switch(status)
1097
1098 for (auto const& c : this->cards)
1099 hand.playcard(c);
1100
1101 this->force_redraw();
1102 this->table().draw_all();
1103 break;
1104 }
1105
1106 default:
1107 break;
1108 } // switch (button_event->button)
1109 break;
1110
1111 case Action::return_cards:
1112 switch (button_event->button) {
1113 case 1: // left mouse button
1114 this->shift_cards = true;
1115 accepted = true;
1116 break;
1117 default:
1118 break;
1119 } // switch (button_event->button)
1120 break;
1121 case Action::get_cards_back:
1122 switch (button_event->button) {
1123 case 1: // left mouse button
1124 this->accept_cards = true;
1125 accepted = true;
1126 break;
1127 default:
1128 break;
1129 } // switch (button_event->button)
1130 break;
1131 }; // switch (this->possible_actions(button_event->x, button_event->y)) {
1132
1133 return accepted;
1134 } // bool Poverty::button_press_event(GdkEventButton* button_event)
1135
1136 /** -> result
1137 **
1138 ** @param x x position
1139 ** @param y y position
1140 **
1141 ** @return what action a can be made at the position (for the mouse)
1142 **/
1143 Poverty::Action
possible_action(int const x,int const y) const1144 Poverty::possible_action(int const x, int const y) const
1145 {
1146 if ( (this->status == Status::invalid)
1147 || (this->status == Status::finished))
1148 return Action::none;
1149
1150 if (!this->player)
1151 return Action::none;
1152
1153 if ( (this->player->type() != Player::Type::human)
1154 && (this->status == Status::shifting))
1155 return Action::shift_cards;
1156
1157 if (this->player->type() != Player::Type::human)
1158 return Action::none;
1159
1160 if (this->shifting_valid()) {
1161 // test whether mouse is over the arrow
1162 auto const outline = this->geometry_arrow().outline();
1163 if ( (x >= outline.x())
1164 && (x <= outline.x() + outline.width())
1165 && (y >= outline.y())
1166 && (y <= outline.y() + outline.height())
1167 ) {
1168 switch(this->status) {
1169 case Status::invalid:
1170 return Action::none;
1171 case Status::shifting:
1172 return Action::shift_cards;
1173 case Status::shifting_back:
1174 return Action::return_cards;
1175 case Status::asking:
1176 return Action::shift_cards;
1177 case Status::getting_back:
1178 case Status::accepted:
1179 case Status::denied_by_all:
1180 case Status::finished:
1181 return Action::none;
1182 } // switch(this->status)
1183 } // if (arrow clicked)
1184 } // test whether the mouse is over the arrow
1185
1186 { // test whether the mouse is over a card in the middle
1187 auto const cardno = this->cardno_at_position(x, y);
1188
1189 if (cardno) {
1190 auto const& card = this->cards[*cardno];
1191 // special case: fill up the cards in the middle
1192 if ( ::bug_report_replay
1193 && ( (this->status == Status::shifting)
1194 || (this->status == Status::shifting_back) )
1195 && card.is_empty() )
1196 return Action::fill_up;
1197
1198 switch(this->status) {
1199 case Status::invalid:
1200 DEBUG_ASSERTION(false,
1201 "Poverty::possible_action():\n"
1202 " 'this->status' == invalid");
1203 break;
1204 case Status::shifting:
1205 // move a non-trump card in the hand
1206 if (card.is_empty())
1207 return Action::none;
1208
1209 if (card.istrump())
1210 return Action::none;
1211
1212 return Action::take_card;
1213
1214 case Status::asking:
1215 return Action::accept_cards;
1216
1217 case Status::shifting_back:
1218 // move a card in the hand
1219 if (card.is_empty())
1220 return Action::none;
1221
1222 return Action::take_card;
1223
1224 case Status::getting_back:
1225 return Action::get_cards_back;
1226
1227 case Status::accepted:
1228 case Status::denied_by_all:
1229 case Status::finished:
1230 break;
1231 } // switch(this->status)
1232 } // if (cardno)
1233 } // test whether the mouse is over a card in the middle
1234
1235 { // test whether the player can move a card from the hand into the middle
1236 if ((this->status == Status::shifting)
1237 || (this->status == Status::shifting_back))
1238 if (!(this->middle_full())) {
1239 auto const cardno
1240 = this->hand().cardno_at_position(x, y);
1241 if (cardno)
1242 return Action::put_card;
1243 } // if (middle has a free place)
1244
1245 // look, whether there is space
1246 } // test whether the player can move a card from the hand
1247
1248 return Action::none;
1249 } // Poverty::Action Poverty::possible_action(int x, int y) const
1250
1251 /** moves the card at 'pos' in the middle
1252 **
1253 ** @param cardno number of cards to shift
1254 **/
1255 void
add_card_to_shift(unsigned const cardno)1256 Poverty::add_card_to_shift(unsigned const cardno)
1257 {
1258 Player& player = const_cast<Player&>(*this->player);
1259 auto hand = player.hand().sorted();
1260
1261 // search the first free card in the middle
1262 auto c = find(this->cards, Card());
1263
1264 if (c == this->cards.end())
1265 // no free card found - ignore the taking of the card
1266 return ;
1267
1268 DEBUG_ASSERTION((hand.played(cardno) == false),
1269 "Poverty::add_card_to_shift(cardno):\n"
1270 " card number '" << cardno << "' is already played:\n"
1271 "Hand:\n"
1272 << hand);
1273
1274 // move the card in the middle
1275 *c = hand.card_all(cardno);
1276 hand.playcard(hand.pos_all_to_pos(cardno));
1277
1278 #ifdef WORKAROUND
1279 { // just testing
1280 unsigned selected_cards = 0;
1281 for (auto const& c : this->cards)
1282 if (!c.is_empty())
1283 selected_cards += 1;
1284
1285 DEBUG_ASSERTION((hand.cardsnumber() + selected_cards
1286 == hand.cardsnumber_all()),
1287 "Poverty::add_card_to_shift(cardno = " << cardno
1288 << ")\n"
1289 " 'hand.cardsnumber() + this->cards.size()' "
1290 " = " << hand.cardsnumber() + this->cards.size()
1291 << " != " << hand.cardsnumber_all()
1292 << " = hand.cardsnumber_all()\n"
1293 << " shifted cards: " << this->cards << '\n'
1294 << " Hand: " << hand);
1295 }
1296 #endif // #ifdef WORKAROUND
1297 } // void Poverty::add_card_to_shift(unsigned cardno)
1298
1299 /** -> result
1300 **
1301 ** @param x x position
1302 ** @param y y position
1303 **
1304 ** @return the number of the card at the position
1305 **/
1306 optional<unsigned>
cardno_at_position(int x,int y) const1307 Poverty::cardno_at_position(int x, int y) const
1308 {
1309 this->geometry_cards().retransform(x, y);
1310 y += this->ui->cards->height();
1311 auto const positions = this->cards_pos();
1312 for (int i = this->cards.size() - 1; i >= 0; --i) {
1313 auto const& pos = positions[i];
1314 if ( (x >= pos.get_x())
1315 && (x < (pos.get_x()
1316 + this->ui->cards->card(this->cards[i]).get_width()))
1317 && (y >= pos.get_y())
1318 && (y < (pos.get_y()
1319 + this->ui->cards->card(this->cards[i]).get_height()))
1320
1321 // ToDo: check for transparency
1322 #ifdef POSTPONED
1323 && !(this->ui->cards->card(cards_drawn[i])
1324 .is_transparent(unsigned(x - pos.get_x()),
1325 unsigned(y - pos.get_y())))
1326 #endif
1327 ) {
1328 return i;
1329 }
1330 } // for (i)
1331
1332 return {};
1333 } // optional<unsigned> Poverty::cardno_at_position(int x, int y) const
1334
1335 /** @return whether the cards are drawn
1336 **/
1337 bool
is_drawn() const1338 Poverty::is_drawn() const
1339 {
1340 if ( (::game_status != GameStatus::game_poverty_shift)
1341 && (::game_status != GameStatus::game_redistribute) )
1342 return false;
1343
1344 if (!this->player)
1345 return false;
1346
1347 if ( (this->status == Status::invalid)
1348 || (this->status == Status::accepted)
1349 || (this->status == Status::finished))
1350 return false;
1351
1352 return true;
1353 } // bool Poverty::is_drawn() const
1354
1355 /** write the status name into the stream
1356 **
1357 ** @param ostr output stream
1358 ** @param status status to write
1359 **
1360 ** @return output stream
1361 **/
1362 ostream&
operator <<(ostream & ostr,Poverty::Status const status)1363 operator<<(ostream& ostr, Poverty::Status const status)
1364 {
1365 switch (status) {
1366 case Poverty::Status::invalid:
1367 return (ostr << "invalid");
1368 case Poverty::Status::shifting:
1369 return (ostr << "shifting");
1370 case Poverty::Status::asking:
1371 return (ostr << "asking");
1372 case Poverty::Status::shifting_back:
1373 return (ostr << "shifting back");
1374 case Poverty::Status::getting_back:
1375 return (ostr << "getting back");
1376 case Poverty::Status::accepted:
1377 return (ostr << "accepted");
1378 case Poverty::Status::denied_by_all:
1379 return (ostr << "denied by all");
1380 case Poverty::Status::finished:
1381 return (ostr << "finished");
1382 } // switch (status)
1383 return ostr;
1384 } // ostream& operator<<(ostream& ostr, Poverty::Status status);
1385
1386 /** write the action name into the stream
1387 **
1388 ** @param ostr output stream
1389 ** @param action action to write
1390 **
1391 ** @return output stream
1392 **/
1393 ostream&
operator <<(ostream & ostr,Poverty::Action const action)1394 operator<<(ostream& ostr, Poverty::Action const action)
1395 {
1396 switch (action) {
1397 case Poverty::Action::none:
1398 return (ostr << "none");
1399 case Poverty::Action::shift_cards:
1400 return (ostr << "shift cards");
1401 case Poverty::Action::accept_cards:
1402 return (ostr << "accept cards");
1403 case Poverty::Action::take_card:
1404 return (ostr << "take cards");
1405 case Poverty::Action::put_card:
1406 return (ostr << "put card");
1407 case Poverty::Action::fill_up:
1408 return (ostr << "fill up");
1409 case Poverty::Action::return_cards:
1410 return (ostr << "return cards");
1411 case Poverty::Action::get_cards_back:
1412 return (ostr << "get cards back");
1413 } // switch (action)
1414 return ostr;
1415 } // ostream& operator<<(ostream& ostr, Poverty::Action action)
1416
1417 } // namespace UI_GTKMM_NS
1418
1419 namespace Gdk {
1420 std::ostream&
operator <<(std::ostream & ostr,Rectangle const & rectangle)1421 operator<<(std::ostream& ostr, Rectangle const& rectangle)
1422 {
1423 ostr << rectangle.get_width() << 'x' << rectangle.get_height()
1424 << '+' << rectangle.get_x() << '+' << rectangle.get_y();
1425 return ostr;
1426 }
1427 std::ostream&
operator <<(std::ostream & ostr,Point const & point)1428 operator<<(std::ostream& ostr, Point const& point)
1429 {
1430 ostr << '+' << point.get_x() << '+' << point.get_y();
1431 return ostr;
1432 }
1433 } // namespace Gdk
1434
1435 #endif // #ifdef USE_UI_GTKMM
1436