Mercurial > libervia-backend
comparison plugins/plugin_misc_tarot.py @ 94:1eb5ccead43c
Tarot game: basic trick
- plugin tarot: it's now possible to make a basic game until the end \o/. Score are calculated but not sent to players yet.
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 01 Jun 2010 18:16:15 +0930 |
parents | 2f87651a5ad8 |
children | be206a3d1a9b |
comparison
equal
deleted
inserted
replaced
93:2f87651a5ad8 | 94:1eb5ccead43c |
---|---|
56 "dependencies": ["XEP_0045"], | 56 "dependencies": ["XEP_0045"], |
57 "main": "Tarot", | 57 "main": "Tarot", |
58 "handler": "yes", | 58 "handler": "yes", |
59 "description": _("""Implementation of Tarot card game""") | 59 "description": _("""Implementation of Tarot card game""") |
60 } | 60 } |
61 | |
62 suits_order = ['pique', 'coeur', 'trefle', 'carreau', 'atout'] #I have swith the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red) | |
63 values_order = map(str,range(1,11))+["valet","cavalier","dame","roi"] | |
64 | |
65 class Card(): | |
66 """This class is used to represent a car logically""" | |
67 #TODO: move this in a library in tools, and share this with frontends (e.g. card_game in wix use the same class) | |
68 | |
69 def __init__(self, tuple_card): | |
70 """@param tuple_card: tuple (suit, value)""" | |
71 self.suit, self.value = tuple_card | |
72 self.bout = True if self.suit=="atout" and self.value in ["1","21","excuse"] else False | |
73 if self.bout or self.value == "roi": | |
74 self.points = 4.5 | |
75 elif self.value == "dame": | |
76 self.points = 3.5 | |
77 elif self.value == "cavalier": | |
78 self.points = 2.5 | |
79 elif self.value == "valet": | |
80 self.points = 1.5 | |
81 else: | |
82 self.points = 0.5 | |
83 | |
84 def get_tuple(self): | |
85 return (self.suit,self.value) | |
86 | |
87 @staticmethod | |
88 def from_tuples(tuple_list): | |
89 result = [] | |
90 for card_tuple in tuple_list: | |
91 result.append(Card(card_tuple)) | |
92 return result | |
93 | |
94 def __cmp__(self, other): | |
95 if other == None: | |
96 return 1 | |
97 if self.suit != other.suit: | |
98 idx1 = suits_order.index(self.suit) | |
99 idx2 = suits_order.index(other.suit) | |
100 return idx1.__cmp__(idx2) | |
101 if self.suit == 'atout': | |
102 if self.value == other.value == 'excuse': | |
103 return 0 | |
104 if self.value == 'excuse': | |
105 return -1 | |
106 if other.value == 'excuse': | |
107 return 1 | |
108 return int(self.value).__cmp__(int(other.value)) | |
109 #at this point we have the same suit which is not 'atout' | |
110 idx1 = values_order.index(self.value) | |
111 idx2 = values_order.index(other.value) | |
112 return idx1.__cmp__(idx2) | |
113 | |
114 def __str__(self): | |
115 return "[%s,%s]" % (self.suit, self.value) | |
61 | 116 |
62 class Tarot(): | 117 class Tarot(): |
63 | 118 |
64 def __init__(self, host): | 119 def __init__(self, host): |
65 info(_("Plugin Tarot initialization")) | 120 info(_("Plugin Tarot initialization")) |
127 field = data_form.Field('list-single', 'contrat', options=map(data_form.Option, self.contrats), required=True) | 182 field = data_form.Field('list-single', 'contrat', options=map(data_form.Option, self.contrats), required=True) |
128 form.addField(field) | 183 form.addField(field) |
129 contrat_elt.addChild(form.toElement()) | 184 contrat_elt.addChild(form.toElement()) |
130 return contrat_elt | 185 return contrat_elt |
131 | 186 |
132 def __next_player(self, game_data): | 187 def __next_player(self, game_data, next_pl = None): |
133 """It's next player turn | 188 """Increment player number & return player name |
134 Increment player number & return player name""" | 189 @param next_pl: if given, then next_player is forced to this one |
135 pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players']) | 190 """ |
136 return game_data['players'][pl_idx] | 191 if next_pl: |
192 game_data['current_player'] = game_data['players'].index(next_pl) | |
193 return next_pl | |
194 else: | |
195 pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players']) | |
196 return game_data['players'][pl_idx] | |
197 | |
198 def __winner(self, game_data): | |
199 """give the nick of the player who win this trick""" | |
200 players_data = game_data['players_data'] | |
201 first = game_data['first_player'] | |
202 first_idx = game_data['players'].index(first) | |
203 suit_asked = None | |
204 strongest = None | |
205 winner = None | |
206 for idx in [(first_idx + i) % 4 for i in range(4)]: | |
207 player = game_data['players'][idx] | |
208 card = players_data[player]['played'] | |
209 if card.value == "excuse": | |
210 continue | |
211 if suit_asked == None: | |
212 suit_asked = card.suit | |
213 if (card.suit == suit_asked or card.suit == "atout") and card > strongest: | |
214 strongest = card | |
215 winner = player | |
216 assert (winner) | |
217 return winner | |
218 | |
219 def __excuse_hack(self, game_data, played, winner): | |
220 """give a low card to other team and keep excuse if trick is lost""" | |
221 #TODO: manage the case where excuse is played on the last trick (and lost) | |
222 #TODO: gof: manage excuse (fool) | |
223 players_data = game_data['players_data'] | |
224 excuse = Card(("atout","excuse")) | |
225 for player in game_data['players']: | |
226 if players_data[player]['wait_for_low']: | |
227 #the excuse owner has to give a card to somebody | |
228 if winner == player: | |
229 #the excuse owner win the trick, we check if we have something to give | |
230 for card in played: | |
231 if card.points == 0.5: | |
232 pl_waiting = players_data[player]['wait_for_low'] | |
233 played.remove(card) | |
234 players_data[pl_waiting]['levees'].append(card) | |
235 debug (_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner":player, "card_waited": card, "player_waiting":pl_waiting}) | |
236 break | |
237 return | |
238 | |
239 if not excuse in played: | |
240 return | |
241 | |
242 excuse_player = None | |
243 for player in game_data['players']: | |
244 if players_data[player]['played'] == excuse: | |
245 excuse_player = player | |
246 break | |
247 | |
248 if excuse_player == winner: | |
249 return #the excuse player win the trick, nothing to do | |
250 | |
251 #first we remove the excuse from played cards | |
252 played.remove(excuse) | |
253 #then we give it back to the original owner | |
254 owner_levees = players_data[excuse_player]['levees'] | |
255 owner_levees.append(excuse) | |
256 #finally we give a low card to the trick winner | |
257 low_card = None | |
258 for card_idx in range(len(owner_levees)-1, -1, -1): | |
259 if owner_levees[card_idx].points == 0.5: | |
260 low_card = owner_levees[card_idx] | |
261 del owner_levees[card_idx] | |
262 players_data[winner]['levees'].append(low_card) | |
263 debug (_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner":excuse_player, "card_waited": low_card, "player_waiting":winner}) | |
264 break | |
265 if not low_card: #The player has no low card yet | |
266 #TODO: manage case when player never win a trick with low card | |
267 players_data[excuse_player]['wait_for_low'] = winner | |
268 debug(_("%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one") % {'excuse_owner':excuse_player, 'winner':winner}) | |
269 | |
270 | |
271 def __calculate_scores(self, game_data): | |
272 """The game is finished, time to know who won :)""" | |
273 players_data = game_data['players_data'] | |
274 levees = players_data[game_data['attaquant']]['levees'] | |
275 score = 0 | |
276 nb_bouts = 0 | |
277 for card in levees: | |
278 if card.bout: | |
279 nb_bouts +=1 | |
280 score += card.points | |
281 point_limit = None | |
282 if nb_bouts == 3: | |
283 point_limit = 36 | |
284 elif nb_bouts == 2: | |
285 point_limit = 41 | |
286 elif nb_bouts == 1: | |
287 point_limit = 51 | |
288 else: | |
289 point_limit = 56 | |
290 victory = (score >= point_limit) | |
291 debug (_('The attacker make %(points)i and need to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s): he %(victory)s') % {'points':score, 'point_limit':point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts>1 else '', 'victory': 'won' if victory else 'lost'}) | |
292 #pdb.set_trace() | |
293 | |
294 | |
137 | 295 |
138 def createGame(self, room_jid_param, players, profile_key='@DEFAULT@'): | 296 def createGame(self, room_jid_param, players, profile_key='@DEFAULT@'): |
139 """Create a new game""" | 297 """Create a new game""" |
140 debug (_("Creating Tarot game")) | 298 debug (_("Creating Tarot game")) |
141 room_jid = jid.JID(room_jid_param) | 299 room_jid = jid.JID(room_jid_param) |
216 game_data = self.games[room_jid.userhost()] | 374 game_data = self.games[room_jid.userhost()] |
217 players = game_data['players'] | 375 players = game_data['players'] |
218 players_data = game_data['players_data'] | 376 players_data = game_data['players_data'] |
219 current_player = game_data['current_player'] | 377 current_player = game_data['current_player'] |
220 game_data['stage'] = "init" | 378 game_data['stage'] = "init" |
379 game_data['first_player'] = None #first player for the current trick | |
221 hand = game_data['hand'] = {} | 380 hand = game_data['hand'] = {} |
222 hand_size = game_data['hand_size'] | 381 hand_size = game_data['hand_size'] |
223 chien = game_data['chien'] = [] | 382 chien = game_data['chien'] = [] |
224 for i in range(4): #TODO: distribute according to real Tarot rules (3 by 3 counter-clockwise, 1 card at once to chien) | 383 for i in range(4): #TODO: distribute according to real Tarot rules (3 by 3 counter-clockwise, 1 card at once to chien) |
225 hand[players[i]] = deck[0:hand_size] | 384 hand[players[i]] = deck[0:hand_size] |
232 mess = self.createGameElt(to_jid) | 391 mess = self.createGameElt(to_jid) |
233 mess.firstChildElement().addChild(self.__list_to_xml(hand[player], 'hand')) | 392 mess.firstChildElement().addChild(self.__list_to_xml(hand[player], 'hand')) |
234 self.host.profiles[profile].xmlstream.send(mess) | 393 self.host.profiles[profile].xmlstream.send(mess) |
235 players_data[player]['contrat'] = None | 394 players_data[player]['contrat'] = None |
236 players_data[player]['levees'] = [] #cards won | 395 players_data[player]['levees'] = [] #cards won |
396 players_data[player]['played'] = None #card on the table | |
397 players_data[player]['wait_for_low'] = None #Used when a player wait for a low card because of excuse | |
237 | 398 |
238 pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players) #the player after the dealer start | 399 pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players) #the player after the dealer start |
239 player = players[pl_idx] | 400 player = players[pl_idx] |
240 to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof: | 401 to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof: |
241 mess = self.createGameElt(to_jid) | 402 mess = self.createGameElt(to_jid) |
288 to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof: | 449 to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof: |
289 mess = self.createGameElt(to_jid) | 450 mess = self.createGameElt(to_jid) |
290 mess.firstChildElement().addChild(self.__ask_contrat()) | 451 mess.firstChildElement().addChild(self.__ask_contrat()) |
291 self.host.profiles[profile].xmlstream.send(mess) | 452 self.host.profiles[profile].xmlstream.send(mess) |
292 else: | 453 else: |
293 #TODO: manage "everybody pass" case | 454 #TODO: gof: manage "everybody pass" case |
294 best_contrat = [None, "Passe"] | 455 best_contrat = [None, "Passe"] |
295 for player in game_data['players']: | 456 for player in game_data['players']: |
296 contrat = players_data[player]['contrat'] | 457 contrat = players_data[player]['contrat'] |
297 idx_best = self.contrats.index(best_contrat[1]) | 458 idx_best = self.contrats.index(best_contrat[1]) |
298 idx_pl = self.contrats.index(contrat) | 459 idx_pl = self.contrats.index(contrat) |
312 del game_data['chien'][:] | 473 del game_data['chien'][:] |
313 | 474 |
314 elif elt.name == 'chien': #we have received the chien | 475 elif elt.name == 'chien': #we have received the chien |
315 debug (_("tarot: chien received")) | 476 debug (_("tarot: chien received")) |
316 data = {"attaquant":elt['attaquant']} | 477 data = {"attaquant":elt['attaquant']} |
317 players_data = game_data['players_data'] | |
318 game_data['stage'] = "ecart" | 478 game_data['stage'] = "ecart" |
319 game_data['attaquant'] = elt['attaquant'] | 479 game_data['attaquant'] = elt['attaquant'] |
320 self.host.bridge.tarotGameShowCards(room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile) | 480 self.host.bridge.tarotGameShowCards(room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile) |
321 | 481 |
322 elif elt.name == 'cards_played': | 482 elif elt.name == 'cards_played': |
323 if game_data['stage'] == "ecart": | 483 if game_data['stage'] == "ecart": |
324 #TODO: check validity of écart (no king, no oulder, cards must be in player hand) | 484 #TODO: check validity of écart (no king, no oulder, cards must be in player hand) |
325 #TODO: show atouts (trumps) if player put some in écart | 485 #TODO: show atouts (trumps) if player put some in écart |
326 assert (game_data['attaquant'] == elt['player']) #TODO: throw an xml error here | 486 assert (game_data['attaquant'] == elt['player']) #TODO: throw an xml error here |
327 players_data[elt['player']]['levees'].extend(self.__xml_to_list(elt)) | 487 players_data[elt['player']]['levees'].extend(Card.from_tuples(self.__xml_to_list(elt))) |
328 game_data['stage'] = "play" | 488 game_data['stage'] = "play" |
329 next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) #the player after the dealer start | 489 next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) #the player after the dealer start |
330 next_player = game_data['players'][next_player_idx] | 490 game_data['first_player'] = next_player = game_data['players'][next_player_idx] |
331 to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof: | 491 to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof: |
332 mess = self.createGameElt(to_jid) | 492 mess = self.createGameElt(to_jid) |
333 yourturn_elt = mess.firstChildElement().addElement('your_turn') | 493 yourturn_elt = mess.firstChildElement().addElement('your_turn') |
334 self.host.profiles[profile].xmlstream.send(mess) | 494 self.host.profiles[profile].xmlstream.send(mess) |
335 elif game_data['stage'] == "play": | 495 elif game_data['stage'] == "play": |
336 current_player = game_data['players'][game_data['current_player']] | 496 current_player = game_data['players'][game_data['current_player']] |
337 #assert (elt['player'] == current_player) #TODO: throw xml error here | 497 #assert (elt['player'] == current_player) #TODO: throw xml error here |
338 cards = self.__xml_to_list(elt) | 498 cards = self.__xml_to_list(elt) |
339 #TODO: check card validity and send error mess if necessary | 499 |
340 if mess_elt['type'] != 'groupchat': | 500 if mess_elt['type'] == 'groupchat': |
501 self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile) | |
502 else: | |
503 #TODO: check card validity and send error mess if necessary | |
341 #the card played is ok, we forward it to everybody | 504 #the card played is ok, we forward it to everybody |
342 #first we remove it from the hand | 505 #first we remove it from the hand and put in on the table |
343 game_data['hand'][current_player].remove(cards[0]) | 506 game_data['hand'][current_player].remove(cards[0]) |
507 players_data[current_player]['played'] = Card(cards[0]) | |
344 | 508 |
345 #then we forward the message | 509 #then we forward the message |
346 mess = self.createGameElt(room_jid) | 510 mess = self.createGameElt(room_jid) |
347 playcard_elt = mess.firstChildElement().addChild(elt) | 511 playcard_elt = mess.firstChildElement().addChild(elt) |
348 self.host.profiles[profile].xmlstream.send(mess) | 512 self.host.profiles[profile].xmlstream.send(mess) |
349 | 513 |
514 #Did everybody played ? | |
515 played = [players_data[player]['played'] for player in game_data['players']] | |
516 if not played.count(None): | |
517 #everybody played | |
518 winner = self.__winner(game_data) | |
519 debug (_('The winner of this trick is %s') % winner) | |
520 #the winner win the trick | |
521 self.__excuse_hack(game_data, played, winner) | |
522 players_data[elt['player']]['levees'].extend(played) | |
523 #nothing left on the table | |
524 for player in game_data['players']: | |
525 players_data[player]['played'] = None | |
526 if len(game_data['hand'][current_player]) == 0: | |
527 #no card lef: the game is finished | |
528 self.__calculate_scores(game_data) | |
529 return | |
530 #next player is the winner | |
531 next_player = game_data['first_player'] = self.__next_player(game_data, winner) | |
532 else: | |
533 next_player = self.__next_player(game_data) | |
534 | |
350 #finally, we tell to the next player to play | 535 #finally, we tell to the next player to play |
351 next_player = self.__next_player(game_data) | |
352 to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof: | 536 to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof: |
353 mess = self.createGameElt(to_jid) | 537 mess = self.createGameElt(to_jid) |
354 yourturn_elt = mess.firstChildElement().addElement('your_turn') | 538 yourturn_elt = mess.firstChildElement().addElement('your_turn') |
355 self.host.profiles[profile].xmlstream.send(mess) | 539 self.host.profiles[profile].xmlstream.send(mess) |
356 else: | 540 |
357 self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile) | |
358 | |
359 | |
360 | |
361 | |
362 elif elt.name == 'your_turn': | 541 elif elt.name == 'your_turn': |
363 self.host.bridge.tarotGameYourTurn(room_jid.userhost(), profile) | 542 self.host.bridge.tarotGameYourTurn(room_jid.userhost(), profile) |
364 | 543 |
365 | 544 |
366 def getHandler(self, profile): | 545 def getHandler(self, profile): |