Mercurial > libervia-web
comparison browser/sat_browser/game_tarot.py @ 1124:28e3eb3bb217
files reorganisation and installation rework:
- files have been reorganised to follow other SàT projects and usual Python organisation (no more "/src" directory)
- VERSION file is now used, as for other SàT projects
- replace the overcomplicated setup.py be a more sane one. Pyjamas part is not compiled anymore by setup.py, it must be done separatly
- removed check for data_dir if it's empty
- installation tested working in virtual env
- libervia launching script is now in bin/libervia
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 25 Aug 2018 17:59:48 +0200 |
parents | src/browser/sat_browser/game_tarot.py@f2170536ba23 |
children | 2af117bfe6cc |
comparison
equal
deleted
inserted
replaced
1123:63a4b8fe9782 | 1124:28e3eb3bb217 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Libervia: a Salut à Toi frontend | |
5 # Copyright (C) 2011-2018 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 import pyjd # this is dummy in pyjs | |
21 from sat.core.log import getLogger | |
22 log = getLogger(__name__) | |
23 | |
24 from sat.core.i18n import _, D_ | |
25 from sat_frontends.tools.games import TarotCard | |
26 from sat_frontends.tools import host_listener | |
27 | |
28 from pyjamas.ui.AbsolutePanel import AbsolutePanel | |
29 from pyjamas.ui.DockPanel import DockPanel | |
30 from pyjamas.ui.SimplePanel import SimplePanel | |
31 from pyjamas.ui.Image import Image | |
32 from pyjamas.ui.Label import Label | |
33 from pyjamas.ui.ClickListener import ClickHandler | |
34 from pyjamas.ui.MouseListener import MouseHandler | |
35 from pyjamas.ui import HasAlignment | |
36 from pyjamas import Window | |
37 from pyjamas import DOM | |
38 from constants import Const as C | |
39 | |
40 import dialog | |
41 import xmlui | |
42 | |
43 | |
44 CARD_WIDTH = 74 | |
45 CARD_HEIGHT = 136 | |
46 CARD_DELTA_Y = 30 | |
47 MIN_WIDTH = 950 # Minimum size of the panel | |
48 MIN_HEIGHT = 500 | |
49 | |
50 | |
51 unicode = str # XXX: pyjama doesn't manage unicode | |
52 | |
53 | |
54 class CardWidget(TarotCard, Image, MouseHandler): | |
55 """This class is used to represent a card, graphically and logically""" | |
56 | |
57 def __init__(self, parent, file_): | |
58 """@param file: path of the PNG file""" | |
59 self._parent = parent | |
60 Image.__init__(self, file_) | |
61 root_name = file_[file_.rfind("/") + 1:-4] | |
62 suit, value = root_name.split('_') | |
63 TarotCard.__init__(self, (suit, value)) | |
64 MouseHandler.__init__(self) | |
65 self.addMouseListener(self) | |
66 | |
67 def onMouseEnter(self, sender): | |
68 if self._parent.state == "ecart" or self._parent.state == "play": | |
69 DOM.setStyleAttribute(self.getElement(), "top", "0px") | |
70 | |
71 def onMouseLeave(self, sender): | |
72 if not self in self._parent.hand: | |
73 return | |
74 if not self in list(self._parent.selected): # FIXME: Workaround pyjs bug, must report it | |
75 DOM.setStyleAttribute(self.getElement(), "top", "%dpx" % CARD_DELTA_Y) | |
76 | |
77 def onMouseUp(self, sender, x, y): | |
78 if self._parent.state == "ecart": | |
79 if self not in list(self._parent.selected): | |
80 self._parent.addToSelection(self) | |
81 else: | |
82 self._parent.removeFromSelection(self) | |
83 elif self._parent.state == "play": | |
84 self._parent.playCard(self) | |
85 | |
86 | |
87 class TarotPanel(DockPanel, ClickHandler): | |
88 | |
89 def __init__(self, parent, referee, players): | |
90 DockPanel.__init__(self) | |
91 ClickHandler.__init__(self) | |
92 self._parent = parent | |
93 self._autoplay = None # XXX: use 0 to activate fake play, None else | |
94 self.referee = referee | |
95 self.players = players | |
96 self.player_nick = parent.nick | |
97 self.bottom_nick = self.player_nick | |
98 idx = self.players.index(self.player_nick) | |
99 idx = (idx + 1) % len(self.players) | |
100 self.right_nick = self.players[idx] | |
101 idx = (idx + 1) % len(self.players) | |
102 self.top_nick = self.players[idx] | |
103 idx = (idx + 1) % len(self.players) | |
104 self.left_nick = self.players[idx] | |
105 self.bottom_nick = self.player_nick | |
106 self.selected = set() # Card choosed by the player (e.g. during ecart) | |
107 self.hand_size = 13 # number of cards in a hand | |
108 self.hand = [] | |
109 self.to_show = [] | |
110 self.state = None | |
111 self.setSize("%dpx" % MIN_WIDTH, "%dpx" % MIN_HEIGHT) | |
112 self.setStyleName("cardPanel") | |
113 | |
114 # Now we set up the layout | |
115 _label = Label(self.top_nick) | |
116 _label.setStyleName('cardGamePlayerNick') | |
117 self.add(_label, DockPanel.NORTH) | |
118 self.setCellWidth(_label, '100%') | |
119 self.setCellHorizontalAlignment(_label, HasAlignment.ALIGN_CENTER) | |
120 | |
121 self.hand_panel = AbsolutePanel() | |
122 self.add(self.hand_panel, DockPanel.SOUTH) | |
123 self.setCellWidth(self.hand_panel, '100%') | |
124 self.setCellHorizontalAlignment(self.hand_panel, HasAlignment.ALIGN_CENTER) | |
125 | |
126 _label = Label(self.left_nick) | |
127 _label.setStyleName('cardGamePlayerNick') | |
128 self.add(_label, DockPanel.WEST) | |
129 self.setCellHeight(_label, '100%') | |
130 self.setCellVerticalAlignment(_label, HasAlignment.ALIGN_MIDDLE) | |
131 | |
132 _label = Label(self.right_nick) | |
133 _label.setStyleName('cardGamePlayerNick') | |
134 self.add(_label, DockPanel.EAST) | |
135 self.setCellHeight(_label, '100%') | |
136 self.setCellHorizontalAlignment(_label, HasAlignment.ALIGN_RIGHT) | |
137 self.setCellVerticalAlignment(_label, HasAlignment.ALIGN_MIDDLE) | |
138 | |
139 self.center_panel = DockPanel() | |
140 self.inner_left = SimplePanel() | |
141 self.inner_left.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) | |
142 self.center_panel.add(self.inner_left, DockPanel.WEST) | |
143 self.center_panel.setCellHeight(self.inner_left, '100%') | |
144 self.center_panel.setCellHorizontalAlignment(self.inner_left, HasAlignment.ALIGN_RIGHT) | |
145 self.center_panel.setCellVerticalAlignment(self.inner_left, HasAlignment.ALIGN_MIDDLE) | |
146 | |
147 self.inner_right = SimplePanel() | |
148 self.inner_right.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) | |
149 self.center_panel.add(self.inner_right, DockPanel.EAST) | |
150 self.center_panel.setCellHeight(self.inner_right, '100%') | |
151 self.center_panel.setCellVerticalAlignment(self.inner_right, HasAlignment.ALIGN_MIDDLE) | |
152 | |
153 self.inner_top = SimplePanel() | |
154 self.inner_top.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) | |
155 self.center_panel.add(self.inner_top, DockPanel.NORTH) | |
156 self.center_panel.setCellHorizontalAlignment(self.inner_top, HasAlignment.ALIGN_CENTER) | |
157 self.center_panel.setCellVerticalAlignment(self.inner_top, HasAlignment.ALIGN_BOTTOM) | |
158 | |
159 self.inner_bottom = SimplePanel() | |
160 self.inner_bottom.setSize("%dpx" % CARD_WIDTH, "%dpx" % CARD_HEIGHT) | |
161 self.center_panel.add(self.inner_bottom, DockPanel.SOUTH) | |
162 self.center_panel.setCellHorizontalAlignment(self.inner_bottom, HasAlignment.ALIGN_CENTER) | |
163 self.center_panel.setCellVerticalAlignment(self.inner_bottom, HasAlignment.ALIGN_TOP) | |
164 | |
165 self.inner_center = SimplePanel() | |
166 self.center_panel.add(self.inner_center, DockPanel.CENTER) | |
167 self.center_panel.setCellHorizontalAlignment(self.inner_center, HasAlignment.ALIGN_CENTER) | |
168 self.center_panel.setCellVerticalAlignment(self.inner_center, HasAlignment.ALIGN_MIDDLE) | |
169 | |
170 self.add(self.center_panel, DockPanel.CENTER) | |
171 self.setCellWidth(self.center_panel, '100%') | |
172 self.setCellHeight(self.center_panel, '100%') | |
173 self.setCellVerticalAlignment(self.center_panel, HasAlignment.ALIGN_MIDDLE) | |
174 self.setCellHorizontalAlignment(self.center_panel, HasAlignment.ALIGN_CENTER) | |
175 | |
176 self.loadCards() | |
177 self.mouse_over_card = None # contain the card to highlight | |
178 self.visible_size = CARD_WIDTH / 2 # number of pixels visible for cards | |
179 self.addClickListener(self) | |
180 | |
181 def loadCards(self): | |
182 """Load all the cards in memory""" | |
183 def _getTarotCardsPathsCb(paths): | |
184 log.debug("_getTarotCardsPathsCb") | |
185 for file_ in paths: | |
186 log.debug(u"path: %s" % file_) | |
187 card = CardWidget(self, file_) | |
188 log.debug(u"card: %s" % card) | |
189 self.cards[(card.suit, card.value)] = card | |
190 self.deck.append(card) | |
191 self._parent.host.bridge.call('tarotGameReady', None, self.player_nick, self.referee) | |
192 self.cards = {} | |
193 self.deck = [] | |
194 self.cards["atout"] = {} # As Tarot is a french game, it's more handy & logical to keep french names | |
195 self.cards["pique"] = {} # spade | |
196 self.cards["coeur"] = {} # heart | |
197 self.cards["carreau"] = {} # diamond | |
198 self.cards["trefle"] = {} # club | |
199 self._parent.host.bridge.call('getTarotCardsPaths', _getTarotCardsPathsCb) | |
200 | |
201 def onClick(self, sender): | |
202 if self.state == "chien": | |
203 self.to_show = [] | |
204 self.state = "wait" | |
205 self.updateToShow() | |
206 elif self.state == "wait_for_ecart": | |
207 self.state = "ecart" | |
208 self.hand.extend(self.to_show) | |
209 self.hand.sort() | |
210 self.to_show = [] | |
211 self.updateToShow() | |
212 self.updateHand() | |
213 | |
214 def tarotGameNewHandler(self, hand): | |
215 """Start a new game, with given hand""" | |
216 if hand is []: # reset the display after the scores have been showed | |
217 self.selected.clear() | |
218 del self.hand[:] | |
219 del self.to_show[:] | |
220 self.state = None | |
221 #empty hand | |
222 self.updateHand() | |
223 #nothing on the table | |
224 self.updateToShow() | |
225 for pos in ['top', 'left', 'bottom', 'right']: | |
226 getattr(self, "inner_%s" % pos).setWidget(None) | |
227 self._parent.host.bridge.call('tarotGameReady', None, self.player_nick, self.referee) | |
228 return | |
229 for suit, value in hand: | |
230 self.hand.append(self.cards[(suit, value)]) | |
231 self.hand.sort() | |
232 self.state = "init" | |
233 self.updateHand() | |
234 | |
235 def updateHand(self): | |
236 """Show the cards in the hand in the hand_panel (SOUTH panel)""" | |
237 self.hand_panel.clear() | |
238 self.hand_panel.setSize("%dpx" % (self.visible_size * (len(self.hand) + 1)), "%dpx" % (CARD_HEIGHT + CARD_DELTA_Y + 10)) | |
239 x_pos = 0 | |
240 y_pos = CARD_DELTA_Y | |
241 for card in self.hand: | |
242 self.hand_panel.add(card, x_pos, y_pos) | |
243 x_pos += self.visible_size | |
244 | |
245 def updateToShow(self): | |
246 """Show cards in the center panel""" | |
247 if not self.to_show: | |
248 _widget = self.inner_center.getWidget() | |
249 if _widget: | |
250 self.inner_center.remove(_widget) | |
251 return | |
252 panel = AbsolutePanel() | |
253 panel.setSize("%dpx" % ((CARD_WIDTH + 5) * len(self.to_show) - 5), "%dpx" % (CARD_HEIGHT)) | |
254 x_pos = 0 | |
255 y_pos = 0 | |
256 for card in self.to_show: | |
257 panel.add(card, x_pos, y_pos) | |
258 x_pos += CARD_WIDTH + 5 | |
259 self.inner_center.setWidget(panel) | |
260 | |
261 def _ecartConfirm(self, confirm): | |
262 if not confirm: | |
263 return | |
264 ecart = [] | |
265 for card in self.selected: | |
266 ecart.append((card.suit, card.value)) | |
267 self.hand.remove(card) | |
268 self.selected.clear() | |
269 self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, ecart) | |
270 self.state = "wait" | |
271 self.updateHand() | |
272 | |
273 def addToSelection(self, card): | |
274 self.selected.add(card) | |
275 if len(self.selected) == 6: | |
276 dialog.ConfirmDialog(self._ecartConfirm, "Put these cards into chien ?").show() | |
277 | |
278 def tarotGameInvalidCardsHandler(self, phase, played_cards, invalid_cards): | |
279 """Invalid cards have been played | |
280 @param phase: phase of the game | |
281 @param played_cards: all the cards played | |
282 @param invalid_cards: cards which are invalid""" | |
283 | |
284 if phase == "play": | |
285 self.state = "play" | |
286 elif phase == "ecart": | |
287 self.state = "ecart" | |
288 else: | |
289 log.error("INTERNAL ERROR: unmanaged game phase") # FIXME: raise an exception here | |
290 | |
291 for suit, value in played_cards: | |
292 self.hand.append(self.cards[(suit, value)]) | |
293 | |
294 self.hand.sort() | |
295 self.updateHand() | |
296 if self._autoplay == None: # No dialog if there is autoplay | |
297 Window.alert('Cards played are invalid !') | |
298 self.__fakePlay() | |
299 | |
300 def removeFromSelection(self, card): | |
301 self.selected.remove(card) | |
302 if len(self.selected) == 6: | |
303 dialog.ConfirmDialog(self._ecartConfirm, "Put these cards into chien ?").show() | |
304 | |
305 def tarotGameChooseContratHandler(self, xml_data): | |
306 """Called when the player has to select his contrat | |
307 @param xml_data: SàT xml representation of the form""" | |
308 body = xmlui.create(self._parent.host, xml_data, flags=['NO_CANCEL']) | |
309 _dialog = dialog.GenericDialog(_('Please choose your contrat'), body, options=['NO_CLOSE']) | |
310 body.setCloseCb(_dialog.close) | |
311 _dialog.show() | |
312 | |
313 def tarotGameShowCardsHandler(self, game_stage, cards, data): | |
314 """Display cards in the middle of the game (to show for e.g. chien ou poignée)""" | |
315 self.to_show = [] | |
316 for suit, value in cards: | |
317 self.to_show.append(self.cards[(suit, value)]) | |
318 self.updateToShow() | |
319 if game_stage == "chien" and data['attaquant'] == self.player_nick: | |
320 self.state = "wait_for_ecart" | |
321 else: | |
322 self.state = "chien" | |
323 | |
324 def getPlayerLocation(self, nick): | |
325 """return player location (top,bottom,left or right)""" | |
326 for location in ['top', 'left', 'bottom', 'right']: | |
327 if getattr(self, '%s_nick' % location) == nick: | |
328 return location | |
329 log.error("This line should not be reached") | |
330 | |
331 def tarotGameCardsPlayedHandler(self, player, cards): | |
332 """A card has been played by player""" | |
333 if not len(cards): | |
334 log.warning("cards should not be empty") | |
335 return | |
336 if len(cards) > 1: | |
337 log.error("can't manage several cards played") | |
338 if self.to_show: | |
339 self.to_show = [] | |
340 self.updateToShow() | |
341 suit, value = cards[0] | |
342 player_pos = self.getPlayerLocation(player) | |
343 player_panel = getattr(self, "inner_%s" % player_pos) | |
344 | |
345 if player_panel.getWidget() != None: | |
346 #We have already cards on the table, we remove them | |
347 for pos in ['top', 'left', 'bottom', 'right']: | |
348 getattr(self, "inner_%s" % pos).setWidget(None) | |
349 | |
350 card = self.cards[(suit, value)] | |
351 DOM.setElemAttribute(card.getElement(), "style", "") | |
352 player_panel.setWidget(card) | |
353 | |
354 def tarotGameYourTurnHandler(self): | |
355 """Called when we have to play :)""" | |
356 if self.state == "chien": | |
357 self.to_show = [] | |
358 self.updateToShow() | |
359 self.state = "play" | |
360 self.__fakePlay() | |
361 | |
362 def __fakePlay(self): | |
363 """Convenience method for stupid autoplay | |
364 /!\ don't forgot to comment any interactive dialog for invalid card""" | |
365 if self._autoplay == None: | |
366 return | |
367 if self._autoplay >= len(self.hand): | |
368 self._autoplay = 0 | |
369 card = self.hand[self._autoplay] | |
370 self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, [(card.suit, card.value)]) | |
371 del self.hand[self._autoplay] | |
372 self.state = "wait" | |
373 self._autoplay += 1 | |
374 | |
375 def playCard(self, card): | |
376 self.hand.remove(card) | |
377 self._parent.host.bridge.call('tarotGamePlayCards', None, self.player_nick, self.referee, [(card.suit, card.value)]) | |
378 self.state = "wait" | |
379 self.updateHand() | |
380 | |
381 def tarotGameScoreHandler(self, xml_data, winners, loosers): | |
382 """Show score at the end of a round""" | |
383 if not winners and not loosers: | |
384 title = "Draw game" | |
385 else: | |
386 if self.player_nick in winners: | |
387 title = "You <b>win</b> !" | |
388 else: | |
389 title = "You <b>loose</b> :(" | |
390 body = xmlui.create(self._parent.host, xml_data, title=title, flags=['NO_CANCEL']) | |
391 _dialog = dialog.GenericDialog(title, body, options=['NO_CLOSE']) | |
392 body.setCloseCb(_dialog.close) | |
393 _dialog.show() | |
394 | |
395 | |
396 ## Menu | |
397 | |
398 def hostReady(host): | |
399 def onTarotGame(): | |
400 def onPlayersSelected(room_jid, other_players): | |
401 other_players = [unicode(contact) for contact in other_players] | |
402 room_jid_s = unicode(room_jid) if room_jid else '' | |
403 host.bridge.launchTarotGame(other_players, room_jid_s, profile=C.PROF_KEY_NONE, callback=lambda dummy: None, errback=host.onJoinMUCFailure) | |
404 dialog.RoomAndContactsChooser(host, onPlayersSelected, 3, title="Tarot", title_invite=_(u"Please select 3 other players"), visible=(False, True)) | |
405 | |
406 def gotMenus(): | |
407 host.menus.addMenu(C.MENU_GLOBAL, (D_(u"Groups"), D_(u"Tarot")), callback=onTarotGame) | |
408 host.addListener('gotMenus', gotMenus) | |
409 | |
410 host_listener.addListener(hostReady) |