Mercurial > libervia-backend
comparison libervia/tui/game_tarot.py @ 4076:b620a8e882e1
refactoring: rename `libervia.frontends.primitivus` to `libervia.tui`
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 16:25:25 +0200 |
parents | libervia/frontends/primitivus/game_tarot.py@26b7ed2817da |
children | 0d7bb4df2343 |
comparison
equal
deleted
inserted
replaced
4075:47401850dec6 | 4076:b620a8e882e1 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 | |
4 # Libervia TUI | |
5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (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 Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from libervia.backend.core.i18n import _ | |
21 import urwid | |
22 from urwid_satext import sat_widgets | |
23 from libervia.frontends.tools.games import TarotCard | |
24 from libervia.frontends.quick_frontend.quick_game_tarot import QuickTarotGame | |
25 from libervia.tui import xmlui | |
26 from libervia.tui.keys import action_key_map as a_key | |
27 | |
28 | |
29 class CardDisplayer(urwid.Text): | |
30 """Show a card""" | |
31 | |
32 signals = ["click"] | |
33 | |
34 def __init__(self, card): | |
35 self.__selected = False | |
36 self.card = card | |
37 urwid.Text.__init__(self, card.get_attr_text()) | |
38 | |
39 def selectable(self): | |
40 return True | |
41 | |
42 def keypress(self, size, key): | |
43 if key == a_key["CARD_SELECT"]: | |
44 self.select(not self.__selected) | |
45 self._emit("click") | |
46 return key | |
47 | |
48 def mouse_event(self, size, event, button, x, y, focus): | |
49 if urwid.is_mouse_event(event) and button == 1: | |
50 self.select(not self.__selected) | |
51 self._emit("click") | |
52 return True | |
53 | |
54 return False | |
55 | |
56 def select(self, state=True): | |
57 self.__selected = state | |
58 attr, txt = self.card.get_attr_text() | |
59 if self.__selected: | |
60 attr += "_selected" | |
61 self.set_text((attr, txt)) | |
62 self._invalidate() | |
63 | |
64 def is_selected(self): | |
65 return self.__selected | |
66 | |
67 def get_card(self): | |
68 return self.card | |
69 | |
70 def render(self, size, focus=False): | |
71 canvas = urwid.CompositeCanvas(urwid.Text.render(self, size, focus)) | |
72 if focus: | |
73 canvas.set_cursor((0, 0)) | |
74 return canvas | |
75 | |
76 | |
77 class Hand(urwid.WidgetWrap): | |
78 """Used to display several cards, and manage a hand""" | |
79 | |
80 signals = ["click"] | |
81 | |
82 def __init__(self, hand=[], selectable=False, on_click=None, user_data=None): | |
83 """@param hand: list of Card""" | |
84 self.__selectable = selectable | |
85 self.columns = urwid.Columns([], dividechars=1) | |
86 if on_click: | |
87 urwid.connect_signal(self, "click", on_click, user_data) | |
88 if hand: | |
89 self.update(hand) | |
90 urwid.WidgetWrap.__init__(self, self.columns) | |
91 | |
92 def selectable(self): | |
93 return self.__selectable | |
94 | |
95 def keypress(self, size, key): | |
96 | |
97 if CardDisplayer in [wid.__class__ for wid in self.columns.widget_list]: | |
98 return self.columns.keypress(size, key) | |
99 else: | |
100 # No card displayed, we still have to manage the clicks | |
101 if key == a_key["CARD_SELECT"]: | |
102 self._emit("click", None) | |
103 return key | |
104 | |
105 def get_selected(self): | |
106 """Return a list of selected cards""" | |
107 _selected = [] | |
108 for wid in self.columns.widget_list: | |
109 if isinstance(wid, CardDisplayer) and wid.is_selected(): | |
110 _selected.append(wid.get_card()) | |
111 return _selected | |
112 | |
113 def update(self, hand): | |
114 """Update the hand displayed in this widget | |
115 @param hand: list of Card""" | |
116 try: | |
117 del self.columns.widget_list[:] | |
118 del self.columns.column_types[:] | |
119 except IndexError: | |
120 pass | |
121 self.columns.contents.append((urwid.Text(""), ("weight", 1, False))) | |
122 for card in hand: | |
123 widget = CardDisplayer(card) | |
124 self.columns.widget_list.append(widget) | |
125 self.columns.column_types.append(("fixed", 3)) | |
126 urwid.connect_signal(widget, "click", self.__on_click) | |
127 self.columns.contents.append((urwid.Text(""), ("weight", 1, False))) | |
128 self.columns.focus_position = 1 | |
129 | |
130 def __on_click(self, card_wid): | |
131 self._emit("click", card_wid) | |
132 | |
133 | |
134 class Card(TarotCard): | |
135 """This class is used to represent a card, logically | |
136 and give a text representation with attributes""" | |
137 | |
138 SIZE = 3 # size of a displayed card | |
139 | |
140 def __init__(self, suit, value): | |
141 """@param file: path of the PNG file""" | |
142 TarotCard.__init__(self, (suit, value)) | |
143 | |
144 def get_attr_text(self): | |
145 """return text representation of the card with attributes""" | |
146 try: | |
147 value = "%02i" % int(self.value) | |
148 except ValueError: | |
149 value = self.value[0].upper() + self.value[1] | |
150 if self.suit == "atout": | |
151 if self.value == "excuse": | |
152 suit = "c" | |
153 else: | |
154 suit = "A" | |
155 color = "neutral" | |
156 elif self.suit == "pique": | |
157 suit = "♠" | |
158 color = "black" | |
159 elif self.suit == "trefle": | |
160 suit = "♣" | |
161 color = "black" | |
162 elif self.suit == "coeur": | |
163 suit = "♥" | |
164 color = "red" | |
165 elif self.suit == "carreau": | |
166 suit = "♦" | |
167 color = "red" | |
168 if self.bout: | |
169 color = "special" | |
170 return ("card_%s" % color, "%s%s" % (value, suit)) | |
171 | |
172 def get_widget(self): | |
173 """Return a widget representing the card""" | |
174 return CardDisplayer(self) | |
175 | |
176 | |
177 class Table(urwid.FlowWidget): | |
178 """Represent the cards currently on the table""" | |
179 | |
180 def __init__(self): | |
181 self.top = self.left = self.bottom = self.right = None | |
182 | |
183 def put_card(self, location, card): | |
184 """Put a card on the table | |
185 @param location: where to put the card (top, left, bottom or right) | |
186 @param card: Card to play or None""" | |
187 assert location in ["top", "left", "bottom", "right"] | |
188 assert isinstance(card, Card) or card == None | |
189 if [getattr(self, place) for place in ["top", "left", "bottom", "right"]].count( | |
190 None | |
191 ) == 0: | |
192 # If the table is full of card, we remove them | |
193 self.top = self.left = self.bottom = self.right = None | |
194 setattr(self, location, card) | |
195 self._invalidate() | |
196 | |
197 def rows(self, size, focus=False): | |
198 return self.display_widget(size, focus).rows(size, focus) | |
199 | |
200 def render(self, size, focus=False): | |
201 return self.display_widget(size, focus).render(size, focus) | |
202 | |
203 def display_widget(self, size, focus): | |
204 cards = {} | |
205 max_col, = size | |
206 separator = " - " | |
207 margin = max((max_col - Card.SIZE) / 2, 0) * " " | |
208 margin_center = max((max_col - Card.SIZE * 2 - len(separator)) / 2, 0) * " " | |
209 for location in ["top", "left", "bottom", "right"]: | |
210 card = getattr(self, location) | |
211 cards[location] = card.get_attr_text() if card else Card.SIZE * " " | |
212 render_wid = [ | |
213 urwid.Text([margin, cards["top"]]), | |
214 urwid.Text([margin_center, cards["left"], separator, cards["right"]]), | |
215 urwid.Text([margin, cards["bottom"]]), | |
216 ] | |
217 return urwid.Pile(render_wid) | |
218 | |
219 | |
220 class TarotGame(QuickTarotGame, urwid.WidgetWrap): | |
221 """Widget for card games""" | |
222 | |
223 def __init__(self, parent, referee, players): | |
224 QuickTarotGame.__init__(self, parent, referee, players) | |
225 self.load_cards() | |
226 self.top = urwid.Pile([urwid.Padding(urwid.Text(self.top_nick), "center")]) | |
227 # self.parent.host.debug() | |
228 self.table = Table() | |
229 self.center = urwid.Columns( | |
230 [ | |
231 ("fixed", len(self.left_nick), urwid.Filler(urwid.Text(self.left_nick))), | |
232 urwid.Filler(self.table), | |
233 ( | |
234 "fixed", | |
235 len(self.right_nick), | |
236 urwid.Filler(urwid.Text(self.right_nick)), | |
237 ), | |
238 ] | |
239 ) | |
240 """urwid.Pile([urwid.Padding(self.top_card_wid,'center'), | |
241 urwid.Columns([('fixed',len(self.left_nick),urwid.Text(self.left_nick)), | |
242 urwid.Padding(self.center_cards_wid,'center'), | |
243 ('fixed',len(self.right_nick),urwid.Text(self.right_nick)) | |
244 ]), | |
245 urwid.Padding(self.bottom_card_wid,'center') | |
246 ])""" | |
247 self.hand_wid = Hand(selectable=True, on_click=self.on_click) | |
248 self.main_frame = urwid.Frame( | |
249 self.center, header=self.top, footer=self.hand_wid, focus_part="footer" | |
250 ) | |
251 urwid.WidgetWrap.__init__(self, self.main_frame) | |
252 self.parent.host.bridge.tarot_game_ready( | |
253 self.player_nick, referee, self.parent.profile | |
254 ) | |
255 | |
256 def load_cards(self): | |
257 """Load all the cards in memory""" | |
258 QuickTarotGame.load_cards(self) | |
259 for value in list(map(str, list(range(1, 22)))) + ["excuse"]: | |
260 card = Card("atout", value) | |
261 self.cards[card.suit, card.value] = card | |
262 self.deck.append(card) | |
263 for suit in ["pique", "coeur", "carreau", "trefle"]: | |
264 for value in list(map(str, list(range(1, 11)))) + ["valet", "cavalier", "dame", "roi"]: | |
265 card = Card(suit, value) | |
266 self.cards[card.suit, card.value] = card | |
267 self.deck.append(card) | |
268 | |
269 def tarot_game_new_handler(self, hand): | |
270 """Start a new game, with given hand""" | |
271 if hand is []: # reset the display after the scores have been showed | |
272 self.reset_round() | |
273 for location in ["top", "left", "bottom", "right"]: | |
274 self.table.put_card(location, None) | |
275 self.parent.host.redraw() | |
276 self.parent.host.bridge.tarot_game_ready( | |
277 self.player_nick, self.referee, self.parent.profile | |
278 ) | |
279 return | |
280 QuickTarotGame.tarot_game_new_handler(self, hand) | |
281 self.hand_wid.update(self.hand) | |
282 self.parent.host.redraw() | |
283 | |
284 def tarot_game_choose_contrat_handler(self, xml_data): | |
285 """Called when the player has to select his contrat | |
286 @param xml_data: SàT xml representation of the form""" | |
287 form = xmlui.create( | |
288 self.parent.host, | |
289 xml_data, | |
290 title=_("Please choose your contrat"), | |
291 flags=["NO_CANCEL"], | |
292 profile=self.parent.profile, | |
293 ) | |
294 form.show(valign="top") | |
295 | |
296 def tarot_game_show_cards_handler(self, game_stage, cards, data): | |
297 """Display cards in the middle of the game (to show for e.g. chien ou poignée)""" | |
298 QuickTarotGame.tarot_game_show_cards_handler(self, game_stage, cards, data) | |
299 self.center.widget_list[1] = urwid.Filler(Hand(self.to_show)) | |
300 self.parent.host.redraw() | |
301 | |
302 def tarot_game_your_turn_handler(self): | |
303 QuickTarotGame.tarot_game_your_turn_handler(self) | |
304 | |
305 def tarot_game_score_handler(self, xml_data, winners, loosers): | |
306 """Called when the round is over, display the scores | |
307 @param xml_data: SàT xml representation of the form""" | |
308 if not winners and not loosers: | |
309 title = _("Draw game") | |
310 else: | |
311 title = _("You win \o/") if self.player_nick in winners else _("You loose :(") | |
312 form = xmlui.create( | |
313 self.parent.host, | |
314 xml_data, | |
315 title=title, | |
316 flags=["NO_CANCEL"], | |
317 profile=self.parent.profile, | |
318 ) | |
319 form.show() | |
320 | |
321 def tarot_game_invalid_cards_handler(self, phase, played_cards, invalid_cards): | |
322 """Invalid cards have been played | |
323 @param phase: phase of the game | |
324 @param played_cards: all the cards played | |
325 @param invalid_cards: cards which are invalid""" | |
326 QuickTarotGame.tarot_game_invalid_cards_handler( | |
327 self, phase, played_cards, invalid_cards | |
328 ) | |
329 self.hand_wid.update(self.hand) | |
330 if self._autoplay == None: # No dialog if there is autoplay | |
331 self.parent.host.bar_notify(_("Cards played are invalid !")) | |
332 self.parent.host.redraw() | |
333 | |
334 def tarot_game_cards_played_handler(self, player, cards): | |
335 """A card has been played by player""" | |
336 QuickTarotGame.tarot_game_cards_played_handler(self, player, cards) | |
337 self.table.put_card(self.get_player_location(player), self.played[player]) | |
338 self._checkState() | |
339 self.parent.host.redraw() | |
340 | |
341 def _checkState(self): | |
342 if isinstance( | |
343 self.center.widget_list[1].original_widget, Hand | |
344 ): # if we have a hand displayed | |
345 self.center.widget_list[1] = urwid.Filler( | |
346 self.table | |
347 ) # we show again the table | |
348 if self.state == "chien": | |
349 self.to_show = [] | |
350 self.state = "wait" | |
351 elif self.state == "wait_for_ecart": | |
352 self.state = "ecart" | |
353 self.hand.extend(self.to_show) | |
354 self.hand.sort() | |
355 self.to_show = [] | |
356 self.hand_wid.update(self.hand) | |
357 | |
358 ##EVENTS## | |
359 def on_click(self, hand, card_wid): | |
360 """Called when user do an action on the hand""" | |
361 if not self.state in ["play", "ecart", "wait_for_ecart"]: | |
362 # it's not our turn, we ignore the click | |
363 card_wid.select(False) | |
364 return | |
365 self._checkState() | |
366 if self.state == "ecart": | |
367 if len(self.hand_wid.get_selected()) == 6: | |
368 pop_up_widget = sat_widgets.ConfirmDialog( | |
369 _("Do you put these cards in chien ?"), | |
370 yes_cb=self.on_ecart_done, | |
371 no_cb=self.parent.host.remove_pop_up, | |
372 ) | |
373 self.parent.host.show_pop_up(pop_up_widget) | |
374 elif self.state == "play": | |
375 card = card_wid.get_card() | |
376 self.parent.host.bridge.tarot_game_play_cards( | |
377 self.player_nick, | |
378 self.referee, | |
379 [(card.suit, card.value)], | |
380 self.parent.profile, | |
381 ) | |
382 self.hand.remove(card) | |
383 self.hand_wid.update(self.hand) | |
384 self.state = "wait" | |
385 | |
386 def on_ecart_done(self, button): | |
387 """Called when player has finished his écart""" | |
388 ecart = [] | |
389 for card in self.hand_wid.get_selected(): | |
390 ecart.append((card.suit, card.value)) | |
391 self.hand.remove(card) | |
392 self.hand_wid.update(self.hand) | |
393 self.parent.host.bridge.tarot_game_play_cards( | |
394 self.player_nick, self.referee, ecart, self.parent.profile | |
395 ) | |
396 self.state = "wait" | |
397 self.parent.host.remove_pop_up() |