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 "icongroup.h"
33 #include "table.h"
34 #include "ui.h"
35 
36 #include "cards.h"
37 #include "icons.h"
38 #include "hand.h"
39 #include "name.h"
40 
41 #include "../../game/game.h"
42 #include "../../party/rule.h"
43 #include "../../player/player.h"
44 #include "../../player/human/human.h"
45 #include "../../misc/preferences.h"
46 
47 namespace UI_GTKMM_NS {
48 
49 /** constructor
50  **
51  ** @param    table   the table
52  ** @param    position   the position of the icongroup
53  **/
Icongroup(Table & table,Position const position)54 Icongroup::Icongroup(Table& table, Position const position) :
55   HTIN(table, position)
56 { }
57 
58 /** destructor
59  **/
60 Icongroup::~Icongroup()
61 = default;
62 
63 /**
64  **/
65 bool
changed() const66 Icongroup::changed() const
67 {
68   if (!this->surface_)
69     return true;
70   if (::game_status & GameStatus::game)
71     return (this->last_status_ != this->current_status());
72   return true;
73 } // bool Icongroup::changed() const
74 
75 /** draws the icongroup
76  **
77  ** @param    cr   drawing context
78  **/
79 void
draw(Cairo::RefPtr<::Cairo::Context> cr)80 Icongroup::draw(Cairo::RefPtr<::Cairo::Context> cr)
81 {
82   cr->save();
83   cr->push_group();
84   this->last_status_ = this->current_status();
85   this->draw_team(cr);
86   this->draw_swines(cr);
87   this->draw_announcement(cr);
88   cr->pop_group_to_source();
89   cr->paint();
90   cr->restore();
91 
92   return ;
93 } // void Icongroup::draw(Cairo::RefPtr<::Cairo::Context> cr)
94 
95 /** draws the team/gametype icon
96  **
97  ** @param    cr       cairo context
98  **
99  ** @todo   silent marriage
100  **/
101 void
draw_team(Cairo::RefPtr<::Cairo::Context> cr)102 Icongroup::draw_team(Cairo::RefPtr<::Cairo::Context> cr)
103 {
104   if(!(::game_status & GameStatus::game))
105     return ;
106 
107   Game const& game = this->game();
108   if (   (::game_status < GameStatus::game_reservation)
109       && !(   game.players().exists_soloplayer()
110            && (this->player() == game.players().soloplayer()) )  )
111     return ;
112   if (!this->last_status_.team_icon)
113     return ;
114 
115   auto icon = this->ui->icons->icon(this->last_status_.team_icon);
116   if (!icon)
117     return;
118 
119   if (this->last_status_.team_guessed) {
120     Glib::RefPtr<Gdk::Pixbuf> copy = icon->copy();
121     icon->saturate_and_pixelate(copy, 5, true);
122     icon = copy;
123   }
124 
125   cr->save();
126   cr->push_group();
127   auto geometry = this->geometry();
128   geometry.transform(cr);
129   draw_pixbuf(cr, icon,
130               (  geometry.flip_x()
131                ? geometry.width() - icon->get_width()
132                : 0),
133               -icon->get_height());
134   cr->pop_group_to_source();
135   cr->paint();
136   cr->restore();
137 } // void Icongroup::draw_team(Cairo::RefPtr<::Cairo::Context> cr)
138 
139 /** draws the announcement icon
140  **
141  ** @param    cr       cairo context
142  **/
143 void
draw_announcement(Cairo::RefPtr<::Cairo::Context> cr)144 Icongroup::draw_announcement(Cairo::RefPtr<::Cairo::Context> cr)
145 {
146   if (! (   (::game_status & GameStatus::game)
147          && (::game_status > GameStatus::game_reservation)) )
148     return ;
149 
150   auto const& announcement = this->last_status_.announcement;
151   if (announcement == Announcement::noannouncement)
152     return ;
153 
154   auto icon = this->ui->icons->icon(announcement);
155   if (this->last_status_.next_announcement) {
156     Glib::RefPtr<Gdk::Pixbuf> copy = icon->copy();
157     icon->saturate_and_pixelate(copy, 4, true);
158     icon = copy;
159   }
160 
161   cr->save();
162   cr->push_group();
163   auto const geometry = this->geometry();
164   geometry.transform(cr);
165   draw_pixbuf(cr, icon,
166                (geometry.width() - icon->get_width()) / 2,
167               -icon->get_height());
168   cr->pop_group_to_source();
169   cr->paint();
170   cr->restore();
171 } // void Icongroup::draw_announcement(Cairo::RefPtr<::Cairo::Context> cr)
172 
173 /** draws the swines/hyperswines icon
174  **
175  ** @param    cr       cairo context
176  **/
177 void
draw_swines(Cairo::RefPtr<::Cairo::Context> cr)178 Icongroup::draw_swines(Cairo::RefPtr<::Cairo::Context> cr)
179 {
180   if (!(::game_status & GameStatus::game))
181     return ;
182 
183   Glib::RefPtr<Gdk::Pixbuf> icon;
184 
185   auto const& swines = this->last_status_.swines;
186   auto const& hyperswines = this->last_status_.hyperswines;
187   auto const& trumpcolor = this->last_status_.trumpcolor;
188   if (swines && hyperswines) {
189     icon = this->ui->icons->swines_hyperswines(trumpcolor);
190   } else if (swines) {
191     icon = this->ui->icons->swines(trumpcolor);
192   } else if (hyperswines) {
193     icon = this->ui->icons->hyperswines(trumpcolor);
194   } else {
195     return ;
196   }
197 
198   auto const geometry = this->geometry();
199   cr->save();
200   cr->push_group();
201   geometry.transform(cr);
202   draw_pixbuf(cr, icon,
203               (  geometry.flip_x()
204                ? 0
205                : geometry.width() - icon->get_width()),
206               -icon->get_height());
207 
208   cr->pop_group_to_source();
209   cr->paint();
210   cr->restore();
211 } // void Icongroup::draw_swines(Cairo::RefPtr<::Cairo::Context> cr)
212 
213 /** @return   the geometry of the icongroup
214  **/
215 Icongroup::Geometry
geometry() const216 Icongroup::geometry() const
217 {
218   Geometry geometry;
219   auto const hand_geometry = this->hand().geometry();
220     geometry.height = [=](){
221       return this->ui->icons->max_height();
222     };
223   switch (this->position()) {
224   case Position::south:
225     geometry.rotation = Rotation::up;
226     geometry.width = [=](){
227       return hand_geometry.width() * 3 / 4;
228     };
229     geometry.pos_x = [=](){
230       return (hand_geometry.pos_x()
231               + hand_geometry.width() / 4);
232     };
233     geometry.pos_y = [=](){
234       return (hand_geometry.pos_y()
235               - hand_geometry.height()
236               - hand_geometry.margin_y());
237     };
238     break;
239 
240   case Position::north:
241     geometry.rotation = Rotation::down;
242     geometry.flip_x = [](){
243       return true;
244     };
245     geometry.width = [=](){
246       return hand_geometry.width() * 3 / 4;
247     };
248     geometry.pos_x = [=](){
249       return hand_geometry.pos_x();
250     };
251     geometry.pos_y = [=](){
252       return (hand_geometry.pos_y()
253               + hand_geometry.height()
254               + hand_geometry.margin_y());
255     };
256     break;
257 
258   case Position::west:
259     geometry.rotation = Rotation::right;
260     geometry.pos_x = [=](){
261       return (hand_geometry.pos_x()
262               + hand_geometry.height()
263               + hand_geometry.margin_y());
264     };
265     geometry.pos_y = [=](){
266       return (hand_geometry.pos_y());
267     };
268     geometry.width = [=](){
269       return hand_geometry.width();
270     };
271     break;
272 
273   case Position::east:
274     geometry.rotation = Rotation::left;
275     geometry.pos_x = [=](){
276       return (hand_geometry.pos_x()
277               - hand_geometry.height()
278               - hand_geometry.margin_y());
279     };
280     geometry.pos_y = [=](){
281       return (hand_geometry.pos_y());
282     };
283     geometry.width = [=](){
284       return hand_geometry.width();
285     };
286     break;
287 
288   case Position::center:
289     DEBUG_ASSERTION(false,
290                     "Icongroup::geometry()\n"
291                     "  wrong position 'center' "
292                     << "(" << this->position() << ")");
293   } // switch (this->position())
294 
295   switch (this->table().layout()) {
296   case Table::Layout::standard:
297     break;
298   case Table::Layout::widescreen:
299     switch (this->position()) {
300     case Position::north:
301       geometry.width = [=](){
302         return hand_geometry.width();
303       };
304       break;
305     case Position::east:
306       geometry.width = [=](){
307         return (hand_geometry.width()
308                 + hand_geometry.margin_x()
309                 + this->name().geometry().height());
310       };
311       break;
312     default:
313       break;
314     } // switch (this->position())
315   } // switch (this->table().layout())
316   return geometry;
317 } // Icongroup::Geometry Icongroup::geometry() const
318 
319 /** @return   the geometry of the icongroup
320  **/
321 Icongroup::Outline
outline() const322 Icongroup::outline() const
323 {
324   return this->geometry().outline();
325 }
326 
327 /** @return   whether the mouse is over the next announcement
328  **/
329 bool
mouse_over_next_announcement() const330 Icongroup::mouse_over_next_announcement() const
331 {
332   if (!::preferences(::Preferences::Type::announce_in_table))
333     return false;
334 
335   if (!::is_real(this->player().team()))
336     return false;
337 
338   Announcement const announcement
339     = this->player().next_announcement();
340 
341   if (announcement == Announcement::noannouncement)
342     return false;
343 
344   int x, y;
345   this->table().get_pointer(x, y);
346 
347   Glib::RefPtr<Gdk::Pixbuf> icon
348     = this->ui->icons->icon(announcement);
349   auto const geometry = this->geometry();
350   geometry.retransform(x, y);
351   return (   (x >= (geometry.width() - icon->get_width()) / 2)
352           && (x < (geometry.width() + icon->get_width()) / 2)
353           && (y < 0)
354           && (y >= -icon->get_height()) );
355 } // bool Icongroup::mouse_over_next_announcement() const
356 
357 /** @return   the current status
358  **/
359 Icongroup::Status
current_status() const360 Icongroup::current_status() const
361 {
362 #ifdef TEST_LAYOUT
363   return {Icons::Type::thrown_nines_and_kings, false,
364     Announcement::no120, false,
365     Card::Color::club, true, true};
366 #endif
367   auto const& game = this->game();
368   Team const team = game.teaminfo().get_human_teaminfo(this->player());
369   auto const* const soloplayer
370     = (game.players().exists_soloplayer()
371        ? &game.players().soloplayer()
372        : ((game.type() == GameType::normal)
373           ? nullptr
374           : &game.players().current_player()));
375 
376   auto team_icon = Icons::Type::none;
377   auto team_guessed = false;
378   if (::game_status == GameStatus::game_reservation) {
379     if (this->player().type() == Player::Type::human) {
380       ::Reservation const& reservation = this->player().reservation();
381       switch (reservation.game_type) {
382       case GameType::normal:
383         team_icon = Icons::type(this->player().hand().contains(Card::club_queen)
384                                 ? Team::re : Team::contra);
385         break;
386       case GameType::poverty:
387         {
388           switch (this->player().hand().count_trumps()) {
389           case 0:
390             team_icon = Icons::Type::poverty_trumps_0;
391             break;
392           case 1:
393             team_icon = Icons::Type::poverty_trumps_1;
394             break;
395           case 2:
396             team_icon = Icons::Type::poverty_trumps_2;
397             break;
398           case 3:
399           case 4:
400           case 5:
401             team_icon = Icons::Type::poverty_trumps_3;
402             break;
403           case UINT_MAX:
404             team_icon = Icons::Type::poverty;
405             break;
406           default:
407             DEBUG_ASSERTION(false,
408                             "IconGroup::draw_team():\n"
409                             "  gametype: poverty\n"
410                             "  number of poverty cards for player " << this->player().no() << ' ' << this->player().name() << " invalid: "
411                             << this->player().hand().count_trumps() << '\n'
412                             << "reservation:\n"
413                             << this->player().reservation() << '\n'
414                             << "game status: " << ::game_status);
415             team_icon = Icons::Type::poverty;
416             break;
417           } // switch (this->player().hand().count_trumps())
418         }
419         break;
420       case GameType::genscher:
421         team_icon = Icons::Type::genscher;
422         break;
423       case GameType::marriage:
424         team_icon = Icons::type(reservation.marriage_selector);
425         break;
426       case GameType::marriage_solo:
427       case GameType::marriage_silent:
428       case GameType::thrown_nines:
429       case GameType::thrown_kings:
430       case GameType::thrown_nines_and_kings:
431       case GameType::thrown_richness:
432       case GameType::fox_highest_trump:
433       case GameType::redistribute:
434       case GameType::solo_diamond:
435       case GameType::solo_jack:
436       case GameType::solo_queen:
437       case GameType::solo_king:
438       case GameType::solo_queen_jack:
439       case GameType::solo_king_jack:
440       case GameType::solo_king_queen:
441       case GameType::solo_koehler:
442       case GameType::solo_club:
443       case GameType::solo_heart:
444       case GameType::solo_spade:
445       case GameType::solo_meatless:
446         team_icon = Icons::type(reservation.game_type);
447         break;
448       } // switch (this->player().reservation().game_type())
449 
450     } // if (this->player().type() == Player::Type::human)
451 
452   } else if (   game.is_finished()
453              || ::preferences(::Preferences::Type::show_soloplayer_in_game)) {
454     switch (game.type()) {
455     case GameType::normal:
456       break;
457     case GameType::poverty:
458       if (this->player() == *soloplayer) {
459         team_icon = Icons::type(game.type());
460       } else if (   (::game_status >= GameStatus::game_start)
461                  && (this->player().team() == Team::re) ) {
462         team_icon = Icons::Type::poverty_partner;
463       }
464       break;
465     case GameType::genscher:
466       if (this->player() == *soloplayer)
467         team_icon = Icons::Type::genscher;
468       else if (this->player().team() == Team::re)
469         team_icon = Icons::type(Team::re);
470       else
471         team_icon = Icons::type(Team::contra);
472       break;
473     case GameType::marriage:
474     case GameType::marriage_solo:
475       if (this->player() == *soloplayer) {
476         team_icon = Icons::type(game.marriage().selector());
477       } else if (game.marriage().selector() == MarriageSelector::team_set) {
478         if (team == Team::re) {
479           team_icon = Icons::Type::marriage_partner;
480         } else {
481           team_icon = Icons::type(team);
482         }
483       } // if !(this->player == soloplayer)
484       break;
485     case GameType::marriage_silent:
486       if (this->player() == *soloplayer) {
487         team_icon = Icons::type(game.type());
488       } else {
489         team_icon = Icons::Type::none;
490       }
491       break;
492     case GameType::thrown_nines:
493     case GameType::thrown_kings:
494     case GameType::thrown_nines_and_kings:
495     case GameType::thrown_richness:
496     case GameType::fox_highest_trump:
497     case GameType::redistribute:
498     case GameType::solo_diamond:
499     case GameType::solo_jack:
500     case GameType::solo_queen:
501     case GameType::solo_king:
502     case GameType::solo_queen_jack:
503     case GameType::solo_king_jack:
504     case GameType::solo_king_queen:
505     case GameType::solo_koehler:
506     case GameType::solo_club:
507     case GameType::solo_heart:
508     case GameType::solo_spade:
509     case GameType::solo_meatless:
510       if (this->player() == *soloplayer)
511         team_icon = Icons::type(game.type());
512       else
513         team_icon = Icons::Type::none;
514       break;
515     } // switch(game.type())
516   } // if (icon shall be shown for soloplayer)
517 
518   // special case: silent marriage
519   if (   !team_icon
520       && (game.players().count_humans() == 1)
521       && (this->player().type() == Player::Type::human)
522       && (   (   (game.type() == GameType::marriage_silent)
523               && (this->player() == game.players().soloplayer()) )
524           || (   (game.type() == GameType::normal)
525               && (this->player().hand().count_all(Card::club_queen)
526                   == game.rule()(Rule::Type::number_of_same_cards)))
527          )
528      ) {
529     team_icon = Icons::type(GameType::marriage_silent);
530   }
531 
532   if (!team_icon) {
533     if (   game.is_finished()
534         || ::preferences(::Preferences::Type::show_known_teams_in_game)) {
535       team_icon = Icons::type(team);
536     } // if (show teams)
537   } // if (!team_icon)
538 
539   if (   !team_icon
540       && ::preferences(::Preferences::Type::show_ai_information_teams)
541       && (game.players().count_humans() == 1)
542       && (game.players().human().teaminfo(this->player()) != Team::unknown)
543       && (game.type() == GameType::normal)
544      ) {
545     team_icon = Icons::type(::maybe_to_team(game.players().human().teaminfo(this->player())));
546     if (!!team_icon
547         && !::is_real(game.players().human().teaminfo(this->player()))) {
548       team_guessed = true;
549     } // if (team is maybe)
550   } // if (team_icon)
551 
552 
553   bool next_announcement = false;
554   auto announcement = this->player().announcement();
555   if (   ::preferences(::Preferences::Type::announce_in_table)
556       && (this->player().type() == Player::Type::human)
557       && (this->mouse_over_next_announcement()
558           || (   (this->player().announcement()
559                   == Announcement::noannouncement)
560               && (this->player().next_announcement()
561                   != Announcement::noannouncement)))) {
562     announcement = this->player().next_announcement();
563     if (   !announcement
564         && this->player().game().announcements().is_valid(Announcement::reply, this->player()))
565       announcement = Announcement::reply;
566     next_announcement = true;
567   }
568 
569   if (announcement == Announcement::reply)
570     announcement = to_reply(this->game().announcements().last(::opposite(this->player().team())));
571 
572   if (this->table().in_game_review()) {
573     announcement = Announcement::noannouncement;
574     for (auto a = this->game().announcements().all(this->player()).rbegin();
575          a != this->game().announcements().all(this->player()).rend();
576          ++a)
577       if (a->trickno < this->table().game_review_trickno()) {
578         announcement = a->announcement;
579         break;
580       }
581   } // if (this->table().in_game_review())
582 
583   // Swines
584 
585   bool swines = this->player().has_swines();
586   bool hyperswines = this->player().has_hyperswines();
587   if (   (::game_status == GameStatus::game_reservation)
588       || (::game_status == GameStatus::game_start) ) {
589     swines = swines || (this->player().reservation().swines
590                         && this->game().swines().swines_announcement_valid(this->player()));
591     hyperswines = hyperswines || (this->player().reservation().hyperswines
592                                   && this->game().swines().hyperswines_announcement_valid(this->player()));
593   }
594 
595   return {team_icon, team_guessed,
596     announcement, next_announcement,
597     this->game().cards().trumpcolor(), swines, hyperswines};
598 } // Icongroup::Status Icongroup::current_status() const
599 
600 /** comparison operator
601  **/
602 bool
operator !=(Icongroup::Status const & lhs,Icongroup::Status const & rhs)603 operator!=(Icongroup::Status const& lhs, Icongroup::Status const& rhs)
604 {
605   return (   (lhs.team_icon != rhs.team_icon)
606           || (lhs.team_guessed != rhs.team_guessed)
607           || (lhs.announcement != rhs.announcement)
608           || (lhs.next_announcement != rhs.next_announcement)
609           || (lhs.trumpcolor != rhs.trumpcolor)
610           || (lhs.swines != rhs.swines)
611           || (lhs.hyperswines != rhs.hyperswines) );
612 } // bool operator!=(Icongroup::Status lhs, Icongroup::Status rhs)
613 
614 /** @return   the geometry of the icongroup
615  **/
616 Icongroup::Outline
outline() const617 Icongroup::Geometry::outline() const
618 {
619   return this->transform({0, -this->height(), this->width(), this->height()});
620 } // Outline Icongroup::Geometry::outline() const
621 
622 } // namespace UI_GTKMM_NS
623 
624 #endif // #ifdef USE_UI_GTKMM
625