comparison src/browser/sat_browser/card_game.py @ 467:97c72fe4a5f2

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