- tarot plugin: new methods/signals tarotGamePlayCards, tarotGameShowCards, tarotGameYourTurn - tarot plugin: protocole update - tarot plugin: family renamed in suit - wix: card_game: card can be selected for écart, card move when mouse is over only if it's our turn
354 lines
17 KiB
Python
Executable file
354 lines
17 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""")
|
|
}
|
|
|
|
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("tarotGameYourTurn", ".communication", signature='ss') #args: room_jid, profile
|
|
self.deck_ordered = []
|
|
for value in ['excuse']+map(str,range(1,22)):
|
|
self.deck_ordered.append(("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((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 __list_to_xml(self, cards_list, elt_name):
|
|
"""Convert a card list (list of tuples) to domish element"""
|
|
cards_list_elt = domish.Element(('',elt_name))
|
|
for suit, value in cards_list:
|
|
card_elt = domish.Element(('','card'))
|
|
card_elt['suit'] = suit
|
|
card_elt['value'] = 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 __next_player(self, game_data):
|
|
"""It's next player turn
|
|
Increment player number & return player name"""
|
|
pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players'])
|
|
return game_data['players'][pl_idx]
|
|
|
|
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:
|
|
status = {}
|
|
players_data = {}
|
|
for player in players:
|
|
players_data[player] = {}
|
|
status[player] = "init"
|
|
self.games[room_jid.userhost()] = {'players':players, 'status':status, 'players_data':players_data, 'hand_size':18, 'init_player':0, 'current_player': 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.__list_to_xml(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"
|
|
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.__list_to_xml(hand[player], 'hand'))
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
players_data[player]['contrat'] = None
|
|
players_data[player]['levees'] = [] #cards won
|
|
|
|
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())
|
|
room_jid = jid.JID(mess_elt['from'])
|
|
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(), room_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: 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]})
|
|
#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.__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'][:]
|
|
|
|
elif elt.name == 'chien': #we have received the chien
|
|
debug (_("tarot: chien received"))
|
|
data = {"attaquant":elt['attaquant']}
|
|
players_data = game_data['players_data']
|
|
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: check validity of écart (no king, no oulder, cards must be in player hand)
|
|
#TODO: show atouts (trumps) if player put some in écart
|
|
assert (game_data['attaquant'] == elt['player']) #TODO: throw an xml error here
|
|
players_data[elt['player']]['levees'].extend(self.__xml_to_list(elt))
|
|
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
|
|
next_player = game_data['players'][next_player_idx]
|
|
to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof:
|
|
mess = self.createGameElt(to_jid)
|
|
self.host.profiles[profile].xmlstream.send(mess)
|
|
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)
|
|
|
|
|
|
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_CB)]
|
|
|
|
def getDiscoItems(self, requestor, target, nodeIdentifier=''):
|
|
return []
|
|
|