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)