- tarot plugin: card validity check, new signal tarotGameInvalidCards - wix: when an invalid cards signal is received, the cards are back in the hand, and the state change so the player as to play again.
770 lines
37 KiB
Python
Executable file
770 lines
37 KiB
Python
Executable file
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
SAT plugin for managing xep-0045
|
|
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.org)
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
from logging import debug, info, warning, error
|
|
from twisted.words.xish import domish
|
|
from twisted.internet import protocol, defer, threads, reactor
|
|
from twisted.words.protocols.jabber import client, jid, xmlstream
|
|
from twisted.words.protocols.jabber import error as jab_error
|
|
from twisted.words.protocols.jabber.xmlstream import IQ
|
|
import os.path
|
|
import pdb
|
|
import random
|
|
|
|
from zope.interface import implements
|
|
|
|
from wokkel import disco, iwokkel, data_form
|
|
from tools.xml_tools import XMLTools
|
|
|
|
from base64 import b64decode
|
|
from hashlib import sha1
|
|
from time import sleep
|
|
|
|
try:
|
|
from twisted.words.protocols.xmlstream import XMPPHandler
|
|
except ImportError:
|
|
from wokkel.subprotocols import XMPPHandler
|
|
|
|
MESSAGE = '/message'
|
|
NS_CG = 'http://www.goffi.org/protocol/card_game'
|
|
CG_TAG = 'card_game'
|
|
CG_REQUEST = MESSAGE + '/' + CG_TAG + '[@xmlns="' + NS_CG + '"]'
|
|
|
|
PLUGIN_INFO = {
|
|
"name": "Tarot cards plugin",
|
|
"import_name": "Tarot",
|
|
"type": "Misc",
|
|
"protocols": [],
|
|
"dependencies": ["XEP_0045"],
|
|
"main": "Tarot",
|
|
"handler": "yes",
|
|
"description": _("""Implementation of Tarot card game""")
|
|
}
|
|
|
|
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)
|
|
values_order = map(str,range(1,11))+["valet","cavalier","dame","roi"]
|
|
|
|
class Card():
|
|
"""This class is used to represent a car logically"""
|
|
#TODO: move this in a library in tools, and share this with frontends (e.g. card_game in wix use the same class)
|
|
|
|
def __init__(self, tuple_card):
|
|
"""@param tuple_card: tuple (suit, value)"""
|
|
self.suit, self.value = tuple_card
|
|
self.bout = True if self.suit=="atout" and self.value in ["1","21","excuse"] else False
|
|
if self.bout or self.value == "roi":
|
|
self.points = 4.5
|
|
elif self.value == "dame":
|
|
self.points = 3.5
|
|
elif self.value == "cavalier":
|
|
self.points = 2.5
|
|
elif self.value == "valet":
|
|
self.points = 1.5
|
|
else:
|
|
self.points = 0.5
|
|
|
|
def get_tuple(self):
|
|
return (self.suit,self.value)
|
|
|
|
@staticmethod
|
|
def from_tuples(tuple_list):
|
|
result = []
|
|
for card_tuple in tuple_list:
|
|
result.append(Card(card_tuple))
|
|
return result
|
|
|
|
def __cmp__(self, other):
|
|
if other == None:
|
|
return 1
|
|
if self.suit != other.suit:
|
|
idx1 = suits_order.index(self.suit)
|
|
idx2 = suits_order.index(other.suit)
|
|
return idx1.__cmp__(idx2)
|
|
if self.suit == 'atout':
|
|
if self.value == other.value == 'excuse':
|
|
return 0
|
|
if self.value == 'excuse':
|
|
return -1
|
|
if other.value == 'excuse':
|
|
return 1
|
|
return int(self.value).__cmp__(int(other.value))
|
|
#at this point we have the same suit which is not 'atout'
|
|
idx1 = values_order.index(self.value)
|
|
idx2 = values_order.index(other.value)
|
|
return idx1.__cmp__(idx2)
|
|
|
|
def __str__(self):
|
|
return "[%s,%s]" % (self.suit, self.value)
|
|
|
|
class Tarot():
|
|
|
|
def __init__(self, host):
|
|
info(_("Plugin Tarot initialization"))
|
|
self.host = host
|
|
self.games={}
|
|
self.contrats = [_('Passe'), _('Petite'), _('Garde'), _('Garde Sans'), _('Garde Contre')]
|
|
host.bridge.addMethod("tarotGameCreate", ".communication", in_sign='sass', out_sign='', method=self.createGame) #args: room_jid, players, profile
|
|
host.bridge.addMethod("tarotGameReady", ".communication", in_sign='sss', out_sign='', method=self.newPlayerReady) #args: player, referee, profile
|
|
host.bridge.addMethod("tarotGameContratChoosed", ".communication", in_sign='ssss', out_sign='', method=self.contratChoosed) #args: player, referee, contrat, profile
|
|
host.bridge.addMethod("tarotGamePlayCards", ".communication", in_sign='ssa(ss)s', out_sign='', method=self.play_cards) #args: player, referee, cards, profile
|
|
host.bridge.addSignal("tarotGameStarted", ".communication", signature='ssass') #args: room_jid, referee, players, profile
|
|
host.bridge.addSignal("tarotGameNew", ".communication", signature='sa(ss)s') #args: room_jid, hand, profile
|
|
host.bridge.addSignal("tarotGameChooseContrat", ".communication", signature='sss') #args: room_jid, xml_data, profile
|
|
host.bridge.addSignal("tarotGameShowCards", ".communication", signature='ssa(ss)a{ss}s') #args: room_jid, type ["chien", "poignée",...], cards, data[dict], profile
|
|
host.bridge.addSignal("tarotGameCardsPlayed", ".communication", signature='ssa(ss)s') #args: room_jid, player, type ["chien", "poignée",...], cards, data[dict], profile
|
|
host.bridge.addSignal("tarotGameYourTurn", ".communication", signature='ss') #args: room_jid, profile
|
|
host.bridge.addSignal("tarotGameScore", ".communication", signature='ssasass') #args: room_jid, xml_data, winners (list of nicks), loosers (list of nicks), profile
|
|
host.bridge.addSignal("tarotGameInvalidCards", ".communication", signature='ssa(ss)a(ss)s') #args: room_jid, game phase, played_cards, invalid_cards, profile
|
|
self.deck_ordered = []
|
|
for value in ['excuse']+map(str,range(1,22)):
|
|
self.deck_ordered.append(Card(("atout",value)))
|
|
for suit in ["pique", "coeur", "carreau", "trefle"]:
|
|
for value in map(str,range(1,11))+["valet","cavalier","dame","roi"]:
|
|
self.deck_ordered.append(Card((suit, value)))
|
|
|
|
def createGameElt(self, to_jid, type="normal"):
|
|
type = "normal" if to_jid.resource else "groupchat"
|
|
elt = domish.Element(('jabber:client','message'))
|
|
elt["to"] = to_jid.full()
|
|
elt["type"] = type
|
|
elt.addElement((NS_CG, CG_TAG))
|
|
return elt
|
|
|
|
def __card_list_to_xml(self, cards_list, elt_name):
|
|
"""Convert a card list to domish element"""
|
|
cards_list_elt = domish.Element(('',elt_name))
|
|
for card in cards_list:
|
|
card_elt = domish.Element(('','card'))
|
|
card_elt['suit'] = card.suit
|
|
card_elt['value'] = card.value
|
|
cards_list_elt.addChild(card_elt)
|
|
return cards_list_elt
|
|
|
|
def __xml_to_list(self, cards_list_elt):
|
|
"""Convert a domish element with cards to a list of tuples"""
|
|
cards_list = []
|
|
for card in cards_list_elt.elements():
|
|
cards_list.append((card['suit'], card['value']))
|
|
return cards_list
|
|
|
|
def __create_started_elt(self, players):
|
|
"""Create a game_started domish element"""
|
|
started_elt = domish.Element(('','started'))
|
|
idx = 0
|
|
for player in players:
|
|
player_elt = domish.Element(('','player'))
|
|
player_elt.addContent(player)
|
|
player_elt['index'] = str(idx)
|
|
idx+=1
|
|
started_elt.addChild(player_elt)
|
|
return started_elt
|
|
|
|
def __ask_contrat(self):
|
|
"""Create a element for asking contrat"""
|
|
contrat_elt = domish.Element(('','contrat'))
|
|
form = data_form.Form('form', title=_('contrat selection'))
|
|
field = data_form.Field('list-single', 'contrat', options=map(data_form.Option, self.contrats), required=True)
|
|
form.addField(field)
|
|
contrat_elt.addChild(form.toElement())
|
|
return contrat_elt
|
|
|
|
def __give_scores(self, scores, winners, loosers):
|
|
"""Create an element to give scores
|
|
@param scores: unicode (can contain line feed)
|
|
@param winners: list of unicode nicks of winners
|
|
@param loosers: list of unicode nicks of loosers"""
|
|
|
|
score_elt = domish.Element(('','score'))
|
|
form = data_form.Form('form', title=_('scores'))
|
|
for line in scores.split('\n'):
|
|
field = data_form.Field('fixed', value = line)
|
|
form.addField(field)
|
|
score_elt.addChild(form.toElement())
|
|
for winner in winners:
|
|
winner_elt = domish.Element(('','winner'))
|
|
winner_elt.addContent(winner)
|
|
score_elt.addChild(winner_elt)
|
|
for looser in loosers:
|
|
looser_elt = domish.Element(('','looser'))
|
|
looser_elt.addContent(looser)
|
|
score_elt.addChild(looser_elt)
|
|
return score_elt
|
|
|
|
def __invalid_cards_elt(self, played_cards, invalid_cards, game_phase):
|
|
"""Create a element for invalid_cards error
|
|
@param list_cards: list of Card
|
|
@param game_phase: phase of the game ['ecart', 'play']"""
|
|
error_elt = domish.Element(('','error'))
|
|
played_elt = self.__card_list_to_xml(played_cards, 'played')
|
|
invalid_elt = self.__card_list_to_xml(invalid_cards, 'invalid')
|
|
error_elt['type'] = 'invalid_cards'
|
|
error_elt['phase'] = game_phase
|
|
error_elt.addChild(played_elt)
|
|
error_elt.addChild(invalid_elt)
|
|
return error_elt
|
|
|
|
def __next_player(self, game_data, next_pl = None):
|
|
"""Increment player number & return player name
|
|
@param next_pl: if given, then next_player is forced to this one
|
|
"""
|
|
if next_pl:
|
|
game_data['current_player'] = game_data['players'].index(next_pl)
|
|
return next_pl
|
|
else:
|
|
pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players'])
|
|
return game_data['players'][pl_idx]
|
|
|
|
def __winner(self, game_data):
|
|
"""give the nick of the player who win this trick"""
|
|
players_data = game_data['players_data']
|
|
first = game_data['first_player']
|
|
first_idx = game_data['players'].index(first)
|
|
suit_asked = None
|
|
strongest = None
|
|
winner = None
|
|
for idx in [(first_idx + i) % 4 for i in range(4)]:
|
|
player = game_data['players'][idx]
|
|
card = players_data[player]['played']
|
|
if card.value == "excuse":
|
|
continue
|
|
if suit_asked == None:
|
|
suit_asked = card.suit
|
|
if (card.suit == suit_asked or card.suit == "atout") and card > strongest:
|
|
strongest = card
|
|
winner = player
|
|
assert winner
|
|
return winner
|
|
|
|
def __excuse_hack(self, game_data, played, winner):
|
|
"""give a low card to other team and keep excuse if trick is lost
|
|
@param game_data: data of the game
|
|
@param played: cards currently on the table
|
|
@param winner: nick of the trick winner"""
|
|
#TODO: manage the case where excuse is played on the last trick (and lost)
|
|
#TODO: gof: manage excuse (fool)
|
|
players_data = game_data['players_data']
|
|
excuse = Card(("atout","excuse"))
|
|
|
|
#we first check if the Excuse was already player
|
|
#and if somebody is waiting for a card
|
|
for player in game_data['players']:
|
|
if players_data[player]['wait_for_low']:
|
|
#the excuse owner has to give a card to somebody
|
|
if winner == player:
|
|
#the excuse owner win the trick, we check if we have something to give
|
|
for card in played:
|
|
if card.points == 0.5:
|
|
pl_waiting = players_data[player]['wait_for_low']
|
|
played.remove(card)
|
|
players_data[pl_waiting]['levees'].append(card)
|
|
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})
|
|
return
|
|
return
|
|
|
|
if not excuse in played:
|
|
#the Excuse is not on the table, nothing to do
|
|
return
|
|
|
|
excuse_player = None #Who has played the Excuse ?
|
|
for player in game_data['players']:
|
|
if players_data[player]['played'] == excuse:
|
|
excuse_player = player
|
|
break
|
|
|
|
if excuse_player == winner:
|
|
return #the excuse player win the trick, nothing to do
|
|
|
|
#first we remove the excuse from played cards
|
|
played.remove(excuse)
|
|
#then we give it back to the original owner
|
|
owner_levees = players_data[excuse_player]['levees']
|
|
owner_levees.append(excuse)
|
|
#finally we give a low card to the trick winner
|
|
low_card = None
|
|
#We look backward in cards won by the Excuse owner to
|
|
#find a low value card
|
|
for card_idx in range(len(owner_levees)-1, -1, -1):
|
|
if owner_levees[card_idx].points == 0.5:
|
|
low_card = owner_levees[card_idx]
|
|
del owner_levees[card_idx]
|
|
players_data[winner]['levees'].append(low_card)
|
|
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})
|
|
break
|
|
if not low_card: #The player has no low card yet
|
|
#TODO: manage case when player never win a trick with low card
|
|
players_data[excuse_player]['wait_for_low'] = winner
|
|
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})
|
|
|
|
|
|
def __calculate_scores(self, game_data):
|
|
"""The game is finished, time to know who won :)
|
|
@param game_data: data of the game
|
|
@return: tuple with (string victory message, list of winners, list of loosers)"""
|
|
players_data = game_data['players_data']
|
|
levees = players_data[game_data['attaquant']]['levees']
|
|
score = 0
|
|
nb_bouts = 0
|
|
bouts = []
|
|
for card in levees:
|
|
if card.bout:
|
|
nb_bouts +=1
|
|
bouts.append(card.value)
|
|
score += card.points
|
|
|
|
#We now check if there is no bug in score calculation
|
|
check_score = 0
|
|
defenseurs = game_data['players'][:]
|
|
defenseurs.remove(game_data['attaquant'])
|
|
for defenseur in defenseurs:
|
|
for card in players_data[defenseur]['levees']:
|
|
check_score+=card.points
|
|
if game_data['contrat'] == "Garde Contre":
|
|
for card in game_data['chien']:
|
|
check_score+=card.points
|
|
assert (score + check_score == 91)
|
|
|
|
point_limit = None
|
|
if nb_bouts == 3:
|
|
point_limit = 36
|
|
elif nb_bouts == 2:
|
|
point_limit = 41
|
|
elif nb_bouts == 1:
|
|
point_limit = 51
|
|
else:
|
|
point_limit = 56
|
|
if game_data['contrat'] == 'Petite':
|
|
contrat_mult = 1
|
|
elif game_data['contrat'] == 'Garde':
|
|
contrat_mult = 2
|
|
elif game_data['contrat'] == 'Garde Sans':
|
|
contrat_mult = 4
|
|
elif game_data['contrat'] == 'Garde Contre':
|
|
contrat_mult = 6
|
|
else:
|
|
error(_('Internal error: contrat not managed (mispelled ?)'))
|
|
|
|
victory = (score >= point_limit)
|
|
margin = score - point_limit
|
|
points_defenseur = (-margin + 25) * contrat_mult
|
|
winners = []
|
|
loosers = []
|
|
player_score = {}
|
|
for player in game_data['players']:
|
|
#TODO: adjust this for 3 and 5 players variants
|
|
#TODO: manage bonuses (petit au bout, poignée, chelem)
|
|
player_score[player] = points_defenseur if player != game_data['attaquant'] else points_defenseur * -3
|
|
players_data[player]['score'] += player_score[player] #we add score of this game to the global score
|
|
if player_score[player] > 0:
|
|
winners.append(player)
|
|
else:
|
|
loosers.append(player)
|
|
|
|
scores_str = _('The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s: %(bouts)s): he %(victory)s') % {'attaquant':game_data['attaquant'], 'points':score, 'point_limit':point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts>1 else '', 'bouts':','.join(map(str,bouts)), 'victory': 'win' if victory else 'loose'}
|
|
scores_str+='\n'
|
|
for player in game_data['players']:
|
|
scores_str+=_("\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i") % {'player':player, 'score_game':player_score[player], 'total_score': players_data[player]['score']}
|
|
debug(scores_str)
|
|
|
|
return (scores_str, winners, loosers)
|
|
|
|
def __invalid_cards(self, game_data, cards):
|
|
"""Checks that the player has the right to play what he wants to
|
|
@param game_data: Game data
|
|
@param cards: cards the player want to play
|
|
@return forbidden_cards cards or empty list if cards are ok"""
|
|
forbidden_cards = []
|
|
if game_data['stage'] == 'ecart':
|
|
for card in cards:
|
|
if card.bout or card.value=="roi":
|
|
forbidden_cards.append(card)
|
|
#TODO: manage case where atouts (trumps) are in the dog
|
|
elif game_data['stage'] == 'play':
|
|
biggest_atout = None
|
|
suit_asked = None
|
|
players = game_data['players']
|
|
players_data = game_data['players_data']
|
|
idx = players.index(game_data['first_player'])
|
|
current_idx = game_data['current_player']
|
|
current_player = players[current_idx]
|
|
if idx == current_idx:
|
|
#the player is the first to play, he can play what he wants
|
|
return forbidden_cards
|
|
while (idx != current_idx):
|
|
player = players[idx]
|
|
played_card = players_data[player]['played']
|
|
if not suit_asked and played_card.value != "excuse":
|
|
suit_asked = played_card.suit
|
|
if played_card.suit == "atout" and played_card > biggest_atout:
|
|
biggest_atout = played_card
|
|
idx = (idx + 1) % len(players)
|
|
has_suit = False #True if there is one card of the asked suit in the hand of the player
|
|
has_atout = False
|
|
biggest_hand_atout = None
|
|
|
|
for hand_card in game_data['hand'][current_player]:
|
|
if hand_card.suit == suit_asked:
|
|
has_suit = True
|
|
if hand_card.suit == "atout":
|
|
has_atout = True
|
|
if hand_card.suit == "atout" and hand_card > biggest_hand_atout:
|
|
biggest_hand_atout = hand_card
|
|
|
|
assert len(cards) == 1
|
|
card = cards[0]
|
|
if card.suit != suit_asked and has_suit and card.value != "excuse":
|
|
forbidden_cards.append(card)
|
|
return forbidden_cards
|
|
if card.suit != suit_asked and card.suit != "atout" and has_atout:
|
|
forbidden_cards.append(card)
|
|
return forbidden_cards
|
|
if card.suit == "atout" and card < biggest_atout and biggest_hand_atout > biggest_atout and card.value != "excuse":
|
|
forbidden_cards.append(card)
|
|
else:
|
|
error(_('Internal error: unmanaged game stage'))
|
|
return forbidden_cards
|
|
|
|
|
|
def __start_play(self, room_jid, game_data, profile):
|
|
"""Start the game (tell to the first player after dealer to play"""
|
|
game_data['stage'] = "play"
|
|
next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) #the player after the dealer start
|
|
game_data['first_player'] = next_player = game_data['players'][next_player_idx]
|
|
to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof:
|
|
mess = self.createGameElt(to_jid)
|
|
yourturn_elt = mess.firstChildElement().addElement('your_turn')
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
|
|
|
|
def createGame(self, room_jid_param, players, profile_key='@DEFAULT@'):
|
|
"""Create a new game"""
|
|
debug (_("Creating Tarot game"))
|
|
room_jid = jid.JID(room_jid_param)
|
|
profile = self.host.memory.getProfileName(profile_key)
|
|
if not profile:
|
|
error (_("profile %s is unknown") % profile_key)
|
|
return
|
|
if False: #gof: self.games.has_key(room_jid):
|
|
warning (_("Tarot game already started in room %s") % room_jid.userhost())
|
|
else:
|
|
room_nick = self.host.plugins["XEP_0045"].getRoomNick(room_jid.userhost(), profile)
|
|
if not room_nick:
|
|
error ('Internal error')
|
|
return
|
|
referee = room_jid.userhost() + '/' + room_nick
|
|
status = {}
|
|
players_data = {}
|
|
for player in players:
|
|
players_data[player] = {'score':0}
|
|
status[player] = "init"
|
|
self.games[room_jid.userhost()] = {'referee':referee, 'players':players, 'status':status, 'players_data':players_data, 'hand_size':18, 'init_player':0, 'current_player': None, 'contrat': None, 'stage': None}
|
|
for player in players:
|
|
mess = self.createGameElt(jid.JID(room_jid.userhost()+'/'+player))
|
|
mess.firstChildElement().addChild(self.__create_started_elt(players))
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
|
|
def newPlayerReady(self, player, referee, profile_key='@DEFAULT@'):
|
|
"""Must be called when player is ready to start a new game"""
|
|
profile = self.host.memory.getProfileName(profile_key)
|
|
if not profile:
|
|
error (_("profile %s is unknown") % profile_key)
|
|
return
|
|
debug ('new player ready: %s' % profile)
|
|
mess = self.createGameElt(jid.JID(referee))
|
|
ready_elt = mess.firstChildElement().addElement('player_ready')
|
|
ready_elt['player'] = player
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
|
|
def contratChoosed(self, player, referee, contrat, profile_key='@DEFAULT@'):
|
|
"""Must be call by player when the contrat is selected
|
|
@param player: player's name
|
|
@param referee: arbiter jid
|
|
@contrat: contrat choosed (must be the exact same string than in the give list options)
|
|
@profile_key: profile
|
|
"""
|
|
profile = self.host.memory.getProfileName(profile_key)
|
|
if not profile:
|
|
error (_("profile %s is unknown") % profile_key)
|
|
return
|
|
debug (_('contrat [%(contrat)s] choosed by %(profile)s') % {'contrat':contrat, 'profile':profile})
|
|
mess = self.createGameElt(jid.JID(referee))
|
|
contrat_elt = mess.firstChildElement().addElement(('','contrat_choosed'), content=contrat)
|
|
contrat_elt['player'] = player
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
|
|
def play_cards(self, player, referee, cards, profile_key='@DEFAULT@'):
|
|
"""Must be call by player when the contrat is selected
|
|
@param player: player's name
|
|
@param referee: arbiter jid
|
|
@cards: cards played (list of tuples)
|
|
@profile_key: profile
|
|
"""
|
|
profile = self.host.memory.getProfileName(profile_key)
|
|
if not profile:
|
|
error (_("profile %s is unknown") % profile_key)
|
|
return
|
|
debug (_('Cards played by %(profile)s: [%(cards)s]') % {'profile':profile,'cards':cards})
|
|
mess = self.createGameElt(jid.JID(referee))
|
|
playcard_elt = mess.firstChildElement().addChild(self.__card_list_to_xml(Card.from_tuples(cards), 'cards_played'))
|
|
playcard_elt['player'] = player
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
|
|
def newGame(self, room_jid, profile):
|
|
"""Launch a new round"""
|
|
debug (_('new Tarot game'))
|
|
deck = self.deck_ordered[:]
|
|
random.shuffle(deck)
|
|
game_data = self.games[room_jid.userhost()]
|
|
players = game_data['players']
|
|
players_data = game_data['players_data']
|
|
current_player = game_data['current_player']
|
|
game_data['stage'] = "init"
|
|
game_data['first_player'] = None #first player for the current trick
|
|
game_data['contrat'] = None
|
|
hand = game_data['hand'] = {}
|
|
hand_size = game_data['hand_size']
|
|
chien = game_data['chien'] = []
|
|
for i in range(4): #TODO: distribute according to real Tarot rules (3 by 3 counter-clockwise, 1 card at once to chien)
|
|
hand[players[i]] = deck[0:hand_size]
|
|
del deck[0:hand_size]
|
|
chien.extend(deck)
|
|
del(deck[:])
|
|
|
|
for player in players:
|
|
to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof:
|
|
mess = self.createGameElt(to_jid)
|
|
mess.firstChildElement().addChild(self.__card_list_to_xml(hand[player], 'hand'))
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
players_data[player]['contrat'] = None
|
|
players_data[player]['levees'] = [] #cards won
|
|
players_data[player]['played'] = None #card on the table
|
|
players_data[player]['wait_for_low'] = None #Used when a player wait for a low card because of excuse
|
|
|
|
pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players) #the player after the dealer start
|
|
player = players[pl_idx]
|
|
to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof:
|
|
mess = self.createGameElt(to_jid)
|
|
mess.firstChildElement().addChild(self.__ask_contrat())
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
|
|
|
|
def card_game_cmd(self, mess_elt, profile):
|
|
print "\n\nCARD GAME command received (profile=%s): %s" % (profile, mess_elt.toXml())
|
|
from_jid = jid.JID(mess_elt['from'])
|
|
room_jid = jid.JID(from_jid.userhost())
|
|
game_elt = mess_elt.firstChildElement()
|
|
game_data = self.games[room_jid.userhost()]
|
|
players_data = game_data['players_data']
|
|
|
|
for elt in game_elt.elements():
|
|
|
|
if elt.name == 'started': #new game created
|
|
players = []
|
|
for player in elt.elements():
|
|
players.append(unicode(player))
|
|
self.host.bridge.tarotGameStarted(room_jid.userhost(), from_jid.full(), players, profile)
|
|
|
|
elif elt.name == 'player_ready': #ready to play
|
|
player = elt['player']
|
|
status = self.games[room_jid.userhost()]['status']
|
|
nb_players = len(self.games[room_jid.userhost()]['players'])
|
|
status[player] = 'ready'
|
|
debug (_('Player %(player)s is ready to start [status: %(status)s]') % {'player':player, 'status':status})
|
|
if status.values().count('ready') == nb_players: #everybody is ready, we can start the game
|
|
self.newGame(room_jid, profile)
|
|
|
|
elif elt.name == 'hand': #a new hand has been received
|
|
self.host.bridge.tarotGameNew(room_jid.userhost(), self.__xml_to_list(elt), profile)
|
|
|
|
elif elt.name == 'contrat': #it's time to choose contrat
|
|
form = data_form.Form.fromElement(elt.firstChildElement())
|
|
xml_data = XMLTools.dataForm2xml(form)
|
|
self.host.bridge.tarotGameChooseContrat(room_jid.userhost(), xml_data, profile)
|
|
|
|
elif elt.name == 'contrat_choosed':
|
|
#TODO: check we receive the contrat from the right person
|
|
#TODO: use proper XEP-0004 way for answering form
|
|
player = elt['player']
|
|
players_data[player]['contrat'] = unicode(elt)
|
|
contrats = [players_data[player]['contrat'] for player in game_data['players']]
|
|
if contrats.count(None):
|
|
#not everybody has choosed his contrat, it's next one turn
|
|
player = self.__next_player(game_data)
|
|
to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof:
|
|
mess = self.createGameElt(to_jid)
|
|
mess.firstChildElement().addChild(self.__ask_contrat())
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
else:
|
|
#TODO: gof: manage "everybody pass" case
|
|
best_contrat = [None, "Passe"]
|
|
for player in game_data['players']:
|
|
contrat = players_data[player]['contrat']
|
|
idx_best = self.contrats.index(best_contrat[1])
|
|
idx_pl = self.contrats.index(contrat)
|
|
if idx_pl > idx_best:
|
|
best_contrat[0] = player
|
|
best_contrat[1] = contrat
|
|
debug (_("%(player)s win the bid with %(contrat)s") % {'player':best_contrat[0],'contrat':best_contrat[1]})
|
|
game_data['contrat'] = best_contrat[1]
|
|
|
|
if game_data['contrat'] == "Garde Sans" or game_data['contrat'] == "Garde Contre":
|
|
self.__start_play(room_jid, game_data, profile)
|
|
game_data['attaquant'] = best_contrat[0]
|
|
else:
|
|
#Time to show the chien to everybody
|
|
to_jid = jid.JID(room_jid.userhost()) #FIXME: gof:
|
|
mess = self.createGameElt(to_jid)
|
|
chien_elt = mess.firstChildElement().addChild(self.__card_list_to_xml(game_data['chien'], 'chien'))
|
|
chien_elt['attaquant'] = best_contrat[0]
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
#the attacker (attaquant) get the chien
|
|
game_data['hand'][best_contrat[0]].extend(game_data['chien'])
|
|
del game_data['chien'][:]
|
|
|
|
if game_data['contrat'] == "Garde Sans":
|
|
#The chien go into attaquant's (attacker) levees
|
|
players_data[best_contrat[0]]['levees'].extend(game_data['chien'])
|
|
del game_data['chien'][:]
|
|
|
|
|
|
elif elt.name == 'chien': #we have received the chien
|
|
debug (_("tarot: chien received"))
|
|
data = {"attaquant":elt['attaquant']}
|
|
game_data['stage'] = "ecart"
|
|
game_data['attaquant'] = elt['attaquant']
|
|
self.host.bridge.tarotGameShowCards(room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile)
|
|
|
|
elif elt.name == 'cards_played':
|
|
if game_data['stage'] == "ecart":
|
|
#TODO: show atouts (trumps) if player put some in écart
|
|
assert (game_data['attaquant'] == elt['player']) #TODO: throw an xml error here
|
|
list_cards = Card.from_tuples(self.__xml_to_list(elt))
|
|
#we now check validity of card
|
|
invalid_cards = self.__invalid_cards(game_data, list_cards)
|
|
if invalid_cards:
|
|
mess = self.createGameElt(jid.JID(room_jid.userhost()+'/'+elt['player']))
|
|
mess.firstChildElement().addChild(self.__invalid_cards_elt(list_cards, invalid_cards, game_data['stage']))
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
return
|
|
|
|
#FIXME: gof: manage Garde Sans & Garde Contre cases
|
|
players_data[elt['player']]['levees'].extend(list_cards) #we add the chien to attaquant's levées
|
|
for card in list_cards:
|
|
game_data['hand'][elt['player']].remove(card)
|
|
|
|
self.__start_play(room_jid, game_data, profile)
|
|
|
|
elif game_data['stage'] == "play":
|
|
current_player = game_data['players'][game_data['current_player']]
|
|
cards = Card.from_tuples(self.__xml_to_list(elt))
|
|
|
|
if mess_elt['type'] == 'groupchat':
|
|
self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile)
|
|
else:
|
|
#we first check validity of card
|
|
invalid_cards = self.__invalid_cards(game_data, cards)
|
|
if invalid_cards:
|
|
mess = self.createGameElt(jid.JID(room_jid.userhost()+'/'+current_player))
|
|
mess.firstChildElement().addChild(self.__invalid_cards_elt(cards, invalid_cards, game_data['stage']))
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
return
|
|
#the card played is ok, we forward it to everybody
|
|
#first we remove it from the hand and put in on the table
|
|
game_data['hand'][current_player].remove(cards[0])
|
|
players_data[current_player]['played'] = cards[0]
|
|
|
|
#then we forward the message
|
|
mess = self.createGameElt(room_jid)
|
|
playcard_elt = mess.firstChildElement().addChild(elt)
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
|
|
#Did everybody played ?
|
|
played = [players_data[player]['played'] for player in game_data['players']]
|
|
if all(played):
|
|
#everybody has played
|
|
winner = self.__winner(game_data)
|
|
debug (_('The winner of this trick is %s') % winner)
|
|
#the winner win the trick
|
|
self.__excuse_hack(game_data, played, winner)
|
|
players_data[elt['player']]['levees'].extend(played)
|
|
#nothing left on the table
|
|
for player in game_data['players']:
|
|
players_data[player]['played'] = None
|
|
if len(game_data['hand'][current_player]) == 0:
|
|
#no card lef: the game is finished
|
|
to_jid = jid.JID(room_jid.userhost()) #FIXME: gof:
|
|
mess = self.createGameElt(to_jid)
|
|
chien_elt = mess.firstChildElement().addChild(self.__give_scores(*self.__calculate_scores(game_data)))
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
return
|
|
#next player is the winner
|
|
next_player = game_data['first_player'] = self.__next_player(game_data, winner)
|
|
else:
|
|
next_player = self.__next_player(game_data)
|
|
|
|
#finally, we tell to the next player to play
|
|
to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof:
|
|
mess = self.createGameElt(to_jid)
|
|
yourturn_elt = mess.firstChildElement().addElement('your_turn')
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
|
|
elif elt.name == 'your_turn':
|
|
self.host.bridge.tarotGameYourTurn(room_jid.userhost(), profile)
|
|
|
|
elif elt.name == 'score':
|
|
form_elt = elt.elements(name='x',uri='jabber:x:data').next()
|
|
winners = []
|
|
loosers = []
|
|
for winner in elt.elements(name='winner', uri=''):
|
|
winners.append(unicode(winner))
|
|
for looser in elt.elements(name='looser', uri=''):
|
|
loosers.append(unicode(looser))
|
|
form = data_form.Form.fromElement(form_elt)
|
|
xml_data = XMLTools.dataForm2xml(form)
|
|
self.host.bridge.tarotGameScore(room_jid.userhost(), xml_data, winners, loosers, profile)
|
|
elif elt.name == 'error':
|
|
if elt['type'] == 'invalid_cards':
|
|
played_cards = self.__xml_to_list(elt.elements(name='played',uri='').next())
|
|
invalid_cards = self.__xml_to_list(elt.elements(name='invalid',uri='').next())
|
|
self.host.bridge.tarotGameInvalidCards(room_jid.userhost(), elt['phase'], played_cards, invalid_cards, profile)
|
|
else:
|
|
error (_('Unmanaged error type: %s') % elt['type'])
|
|
else:
|
|
error (_('Unmanaged card game element: %s') % elt.name)
|
|
|
|
def getHandler(self, profile):
|
|
return CardGameHandler(self)
|
|
|
|
class CardGameHandler (XMPPHandler):
|
|
implements(iwokkel.IDisco)
|
|
|
|
def __init__(self, plugin_parent):
|
|
self.plugin_parent = plugin_parent
|
|
self.host = plugin_parent.host
|
|
|
|
def connectionInitialized(self):
|
|
self.xmlstream.addObserver(CG_REQUEST, self.plugin_parent.card_game_cmd, profile = self.parent.profile)
|
|
|
|
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
|
|
return [disco.DiscoFeature(NS_CG)]
|
|
|
|
def getDiscoItems(self, requestor, target, nodeIdentifier=''):
|
|
return []
|
|
|