comparison src/browser/sat_browser/game_tarot.py @ 679:a90cc8fc9605

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