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