plugins radiocol, card_game, quiz: code factorization

This commit is contained in:
souliane 2013-10-23 12:45:13 +02:00
commit 34947f8840
5 changed files with 284 additions and 379 deletions

View file

@ -30,7 +30,7 @@ from zope.interface import implements
from wokkel import disco, iwokkel, data_form
from sat.tools.xml_tools import dataForm2XML
from sat.tools.frontends.games import TarotCard
from sat.tools.plugins.games import RoomGame
from time import time
try:
@ -55,16 +55,15 @@ PLUGIN_INFO = {
}
class Quiz(object):
class Quiz(RoomGame):
def __init__(self, host):
info(_("Plugin Quiz initialization"))
self.host = host
self.games = {}
self.waiting_inv = {} # Invitation waiting for people to join to launch a game
host.bridge.addMethod("quizGameLaunch", ".plugin", in_sign='ass', out_sign='', method=self.quizGameLaunch) # args: players, profile
host.bridge.addMethod("quizGameCreate", ".plugin", in_sign='sass', out_sign='', method=self.quizGameCreate) # args: room_jid, players, profile
host.bridge.addMethod("quizGameReady", ".plugin", in_sign='sss', out_sign='', method=self.newPlayerReady) # args: player, referee, profile
RoomGame.__init__(self, host, PLUGIN_INFO, (NS_QG, QG_TAG), player_init_data={'score': 0},
options={'stage': None})
host.bridge.addMethod("quizGameLaunch", ".plugin", in_sign='ass', out_sign='', method=self.prepareRoom) # args: players, profile
host.bridge.addMethod("quizGameCreate", ".plugin", in_sign='sass', out_sign='', method=self.createGame) # args: room_jid, players, profile
host.bridge.addMethod("quizGameReady", ".plugin", in_sign='sss', out_sign='', method=self.playerReady) # args: player, referee, profile
host.bridge.addMethod("quizGameAnswer", ".plugin", in_sign='ssss', out_sign='', method=self.playerAnswer)
host.bridge.addSignal("quizGameStarted", ".plugin", signature='ssass') # args: room_jid, referee, players, profile
host.bridge.addSignal("quizGameNew", ".plugin",
@ -117,14 +116,6 @@ class Quiz(object):
'param_2': '%(doc_profile)s'})
host.trigger.add("MUC user joined", self.userJoinedTrigger)
def createGameElt(self, to_jid, type="normal"):
type = "normal" if to_jid.resource else "groupchat"
elt = domish.Element((None, 'message'))
elt["to"] = to_jid.full()
elt["type"] = type
elt.addElement((NS_QG, QG_TAG))
return elt
def __game_data_to_xml(self, game_data):
"""Convert a game data dict to domish element"""
game_data_elt = domish.Element((None, 'game_data'))
@ -172,18 +163,6 @@ class Quiz(object):
return answer_result_elt
def __create_started_elt(self, players):
"""Create a game_started domish element"""
started_elt = domish.Element((None, 'started'))
idx = 0
for player in players:
player_elt = domish.Element((None, 'player'))
player_elt.addContent(player)
player_elt['index'] = str(idx)
idx += 1
started_elt.addChild(player_elt)
return started_elt
def __ask_question(self, question_id, question, timer):
"""Create a element for asking a question"""
question_elt = domish.Element((None, 'question'))
@ -202,100 +181,6 @@ class Quiz(object):
yourturn_elt = mess.firstChildElement().addElement('your_turn')
self.host.profiles[profile].xmlstream.send(mess)
def userJoinedTrigger(self, room, user, profile):
"""This trigger is used to check if we are waiting people in this room,
and to create a game if everybody is here"""
_room_jid = room.occupantJID.userhostJID()
if _room_jid in self.waiting_inv and len(room.roster) == 4:
#When we have 4 people in the room, we create the game
#TODO: check people identity
players = room.roster.keys()
del self.waiting_inv[_room_jid]
self.quizGameCreate(_room_jid.userhost(), players, profile_key=profile)
return True
def quizGameLaunch(self, players, profile_key='@DEFAULT@'):
"""Launch a game: helper method to create a room, invite players, and create the quiz game
@param players: list for players jid"""
debug(_('Launching quiz game'))
profile = self.host.memory.getProfileName(profile_key)
if not profile:
error(_("Unknown profile"))
return
def quizRoomJoined(room):
_room = room.occupantJID.userhostJID()
for player in players:
self.host.plugins["XEP-0249"].invite(jid.JID(player), room.occupantJID.userhostJID(), {"game": "Quiz"}, profile)
self.waiting_inv[_room] = (time(), players) # TODO: remove invitation waiting for too long, using the time data
def after_init(ignore):
room_name = "sat_quiz_%s" % self.host.plugins["XEP-0045"].getUniqueName(profile_key)
print "\n\n===> room_name:", room_name
muc_service = None
for service in self.host.memory.getServerServiceEntities("conference", "text", profile):
if not ".irc." in service.userhost():
#FIXME:
#This awfull ugly hack is here to avoid an issue with openfire: the irc gateway
#use "conference/text" identity (instead of "conference/irc"), there is certainly a better way
#to manage this, but this hack fill do it for test purpose
muc_service = service
break
if not muc_service:
error(_("Can't find a MUC service"))
return
_jid, xmlstream = self.host.getJidNStream(profile)
d = self.host.plugins["XEP-0045"].join(jid.JID("%s@%s" % (room_name, muc_service.userhost())), _jid.user, {}, profile).addCallback(quizRoomJoined)
client = self.host.getClient(profile)
if not client:
error(_('No client for this profile key: %s') % profile_key)
return
client.client_initialized.addCallback(after_init)
def quizGameCreate(self, room_jid_param, players, profile_key='@DEFAULT@'):
"""Create a new game
@param room_jid_param: jid of the room
@param players: list of players nick (nick must exist in the room)
@param profile_key: %(doc_profile_key)s"""
debug(_("Creating Quiz 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 room_jid in self.games:
warning(_("Quiz 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, '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 playerAnswer(self, player, referee, answer, profile_key='@DEFAULT@'):
"""Called when a player give an answer"""
profile = self.host.memory.getProfileName(profile_key)
@ -371,22 +256,12 @@ class Quiz(object):
def newGame(self, room_jid, profile):
"""Launch a new round"""
debug(_('new Quiz game'))
game_data = self.games[room_jid.userhost()]
players = game_data['players']
players_data = game_data['players_data']
game_data['stage'] = "init"
for player in players:
players_data[player]['game_score'] = 0
common_data = {'game_score': 0}
new_game_data = {"instructions": _(u"""Bienvenue dans cette partie rapide de quizz, le premier à atteindre le score de 9 remporte le jeu
Attention, tu es prêt ?""")}
mess = self.createGameElt(room_jid)
mess.firstChildElement().addChild(self.__game_data_to_xml(new_game_data))
self.host.profiles[profile].xmlstream.send(mess)
msg_elts = self.__game_data_to_xml(new_game_data)
RoomGame.newRound(self, room_jid, (common_data, msg_elts), profile)
reactor.callLater(10, self.askQuestion, room_jid, profile)
def quiz_game_cmd(self, mess_elt, profile):

View file

@ -29,7 +29,7 @@ from zope.interface import implements
import os.path
from os import unlink
from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError
from sat.tools.plugins.games import RoomGame
try:
from twisted.words.protocols.xmlstream import XMPPHandler
except ImportError:
@ -54,14 +54,15 @@ PLUGIN_INFO = {
QUEUE_LIMIT = 2
class Radiocol(object):
class Radiocol(RoomGame):
def __init__(self, host):
info(_("Radio collective initialization"))
RoomGame.__init__(self, host, PLUGIN_INFO, (NC_RADIOCOL, RADIOC_TAG),
options={'queue': [], 'upload': True, 'playing': False, 'to_delete': {}})
self.host = host
self.radios = {}
host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='ass', out_sign='', method=self.radiocolLaunch)
host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='ss', out_sign='', method=self.radiocolCreate)
host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='ass', out_sign='', method=self.prepareRoom)
host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='ss', out_sign='', method=self.createCollectiveGame)
host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self.radiocolSongAdded)
host.bridge.addSignal("radiocolStarted", ".plugin", signature='sss') # room_jid, referee, profile
host.bridge.addSignal("radiocolSongRejected", ".plugin", signature='sss') # room_jid, reason, profile
@ -71,19 +72,6 @@ class Radiocol(object):
host.bridge.addSignal("radiocolUploadOk", ".plugin", signature='ss') # room_jid, profile
host.trigger.add("MUC user joined", self.userJoinedTrigger)
def createRadiocolElt(self, to_jid, type="normal"):
type = "normal" if to_jid.resource else "groupchat"
elt = domish.Element((None, 'message'))
elt["to"] = to_jid.full()
elt["type"] = type
elt.addElement((NC_RADIOCOL, RADIOC_TAG))
return elt
def __create_started_elt(self):
"""Create a game_started domish element"""
started_elt = domish.Element((None, 'started'))
return started_elt
def __create_preload_elt(self, sender, filename, title, artist, album):
preload_elt = domish.Element((None, 'preload'))
preload_elt['sender'] = sender
@ -93,82 +81,6 @@ class Radiocol(object):
preload_elt['album'] = album
return preload_elt
def userJoinedTrigger(self, room, user, profile):
"""This trigger is used to check if we are waiting people in this room,
and to create a game if everybody is here"""
room_jid = room.occupantJID.userhost()
if room_jid in self.radios and self.radios[room_jid]["referee"] == room.occupantJID.full():
#we are in a radiocol room, let's start the party !
mess = self.createRadiocolElt(jid.JID(room_jid + '/' + user.nick))
mess.firstChildElement().addChild(self.__create_started_elt())
self.host.profiles[profile].xmlstream.send(mess)
return True
def radiocolLaunch(self, occupants, profile_key='@DEFAULT@'):
"""Launch a game: helper method to create a room, invite occupants, and create the radiocol
@param occupants: list for occupants jid"""
debug(_('Launching radiocol'))
profile = self.host.memory.getProfileName(profile_key)
if not profile:
error(_("Unknown profile"))
return
def radiocolRoomJoined(room):
print "radiocolRoomJoined"
_room_jid = room.occupantJID.userhostJID()
self.radiocolCreate(_room_jid.userhost(), profile_key=profile)
for occupant in occupants:
self.host.plugins["XEP-0249"].invite(jid.JID(occupant), room.occupantJID.userhostJID(), {"game": "Radiocol"}, profile)
def after_init(ignore):
room_name = "sat_radiocol_%s" % self.host.plugins["XEP-0045"].getUniqueName(profile_key)
print "\n\n===> room_name:", room_name
muc_service = None
for service in self.host.memory.getServerServiceEntities("conference", "text", profile):
if not ".irc." in service.userhost():
#FIXME:
#This awfull ugly hack is here to avoid an issue with openfire: the irc gateway
#use "conference/text" identity (instead of "conference/irc"), there is certainly a better way
#to manage this, but this hack fill do it for test purpose
muc_service = service
break
if not muc_service:
error(_("Can't find a MUC service"))
return
_jid, xmlstream = self.host.getJidNStream(profile)
d = self.host.plugins["XEP-0045"].join(jid.JID("%s@%s" % (room_name, muc_service.userhost())), _jid.user, {}, profile)
d.addCallback(radiocolRoomJoined)
client = self.host.getClient(profile)
if not client:
error(_('No client for this profile key: %s') % profile_key)
return
client.client_initialized.addCallback(after_init)
def radiocolCreate(self, room_jid_param, profile_key='@DEFAULT@'):
"""Create a new game
@param room_jid_param: jid of the room
@param profile_key: %(doc_profile_key)s"""
debug(_("Creating Radiocol"))
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 room_jid in self.radios:
warning(_("Radiocol 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
self.radios[room_jid.userhost()] = {'referee': referee, 'queue': [], 'upload': True, 'playing': False, 'occupants_data': {}, 'to_delete': {}}
mess = self.createRadiocolElt(jid.JID(room_jid.userhost()))
mess.firstChildElement().addChild(self.__create_started_elt())
self.host.profiles[profile].xmlstream.send(mess)
def radiocolSongAdded(self, referee, song_path, profile):
"""This method is called by libervia when a song has been uploaded
@param room_jid_param: jid of the room
@ -189,7 +101,7 @@ class Radiocol(object):
unlink(song_path) # FIXME: same host trick (see note above)
self.host.bridge.radiocolSongRejected(jid.JID(referee).userhost(),
"Uploaded file is not Ogg Vorbis song, only Ogg Vorbis songs are acceptable", profile)
"""mess = self.createRadiocolElt(jid.JID(referee))
"""mess = self.createGameElt(jid.JID(referee))
reject_elt = mess.firstChildElement().addElement(('','song_rejected'))
reject_elt['sender'] = client.jid
reject_elt['reason'] = "Uploaded file is not Ogg Vorbis song, only Ogg Vorbis songs are acceptable"
@ -200,7 +112,7 @@ class Radiocol(object):
artist = song.get("artist", ["Unknown"])[0]
album = song.get("album", ["Unknown"])[0]
length = song.info.length
mess = self.createRadiocolElt(jid.JID(referee))
mess = self.createGameElt(jid.JID(referee))
added_elt = mess.firstChildElement().addElement(('', 'song_added'))
added_elt['filename'] = filename = os.path.basename(song_path)
added_elt['title'] = title
@ -209,7 +121,7 @@ class Radiocol(object):
added_elt['length'] = str(length)
self.host.profiles[profile].xmlstream.send(mess)
radio_data = self.radios[jid.JID(referee).userhost()] # FIXME: referee comes from Libervia's client side, it's unsecure
radio_data = self.games[jid.JID(referee).userhost()] # FIXME: referee comes from Libervia's client side, it's unsecure
radio_data['to_delete'][filename] = song_path # FIXME: works only because of the same host trick, see the note under the docstring
def playNext(self, room_jid, profile):
@ -219,7 +131,7 @@ class Radiocol(object):
# and clean the datas/stop the playlist if it's not the case
#TODO: songs need to be erased once played or found invalids
# ==> unlink done the Q&D way with the same host trick (see above)
radio_data = self.radios[room_jid.userhost()]
radio_data = self.games[room_jid.userhost()]
queue = radio_data['queue']
if not queue:
#nothing left to play, we need to wait for uploads
@ -227,14 +139,14 @@ class Radiocol(object):
return
filename, length = queue.pop(0)
mess = self.createRadiocolElt(room_jid)
mess = self.createGameElt(room_jid)
play_elt = mess.firstChildElement().addElement(('', 'play'))
play_elt['filename'] = filename
self.host.profiles[profile].xmlstream.send(mess)
if not radio_data['upload'] and len(queue) < QUEUE_LIMIT:
#upload is blocked and we now have resources to get more, we reactivate it
mess = self.createRadiocolElt(room_jid)
mess = self.createGameElt(room_jid)
no_upload_elt = mess.firstChildElement().addElement(('', 'upload_ok'))
self.host.profiles[profile].xmlstream.send(mess)
radio_data['upload'] = True
@ -254,8 +166,7 @@ class Radiocol(object):
from_jid = jid.JID(mess_elt['from'])
room_jid = jid.JID(from_jid.userhost())
radio_elt = mess_elt.firstChildElement()
radio_data = self.radios[room_jid.userhost()]
occupants_data = radio_data['occupants_data']
radio_data = self.games[room_jid.userhost()]
queue = radio_data['queue']
for elt in radio_elt.elements():
@ -278,7 +189,7 @@ class Radiocol(object):
if len(queue) >= QUEUE_LIMIT:
#there are already too many songs in queue, we reject this one
mess = self.createRadiocolElt(room_jid)
mess = self.createGameElt(room_jid)
reject_elt = mess.firstChildElement().addElement(('', 'song_rejected'))
reject_elt['sender'] = from_jid.resource
reject_elt['reason'] = "Too many songs in queue"
@ -291,13 +202,13 @@ class Radiocol(object):
if len(queue) >= QUEUE_LIMIT:
#We are at the limit, we refuse new upload until next play
mess = self.createRadiocolElt(room_jid)
mess = self.createGameElt(room_jid)
no_upload_elt = mess.firstChildElement().addElement(('', 'no_upload'))
#FIXME: add an error code
self.host.profiles[profile].xmlstream.send(mess)
radio_data['upload'] = False
mess = self.createRadiocolElt(room_jid)
mess = self.createGameElt(room_jid)
preload_elt = self.__create_preload_elt(from_jid.resource,
elt['filename'],
elt['title'],

View file

@ -28,6 +28,7 @@ from wokkel import disco, iwokkel, data_form
from sat.tools.xml_tools import dataForm2XML
from sat.tools.frontends.games import TarotCard
from sat.tools.plugins.games import RoomGame
from time import time
try:
@ -52,17 +53,16 @@ PLUGIN_INFO = {
}
class Tarot(object):
class Tarot(RoomGame):
def __init__(self, host):
info(_("Plugin Tarot initialization"))
self.host = host
self.games = {}
self.waiting_inv = {} # Invitation waiting for people to join to launch a game
RoomGame.__init__(self, host, PLUGIN_INFO, (NS_CG, CG_TAG), player_init_data={'score': 0},
options={'hand_size': 18, 'init_player': 0, 'current_player': None, 'contrat': None, 'stage': None})
self.contrats = [_('Passe'), _('Petite'), _('Garde'), _('Garde Sans'), _('Garde Contre')]
host.bridge.addMethod("tarotGameLaunch", ".plugin", in_sign='ass', out_sign='', method=self.launchGame) # args: room_jid, players, profile
host.bridge.addMethod("tarotGameLaunch", ".plugin", in_sign='ass', out_sign='', method=self.prepareRoom) # args: room_jid, players, profile
host.bridge.addMethod("tarotGameCreate", ".plugin", in_sign='sass', out_sign='', method=self.createGame) # args: room_jid, players, profile
host.bridge.addMethod("tarotGameReady", ".plugin", in_sign='sss', out_sign='', method=self.newPlayerReady) # args: player, referee, profile
host.bridge.addMethod("tarotGameReady", ".plugin", in_sign='sss', out_sign='', method=self.playerReady) # args: player, referee, profile
host.bridge.addMethod("tarotGameContratChoosed", ".plugin", in_sign='ssss', out_sign='', method=self.contratChoosed) # args: player, referee, contrat, profile
host.bridge.addMethod("tarotGamePlayCards", ".plugin", in_sign='ssa(ss)s', out_sign='', method=self.play_cards) # args: player, referee, cards, profile
host.bridge.addSignal("tarotGameStarted", ".plugin", signature='ssass') # args: room_jid, referee, players, profile
@ -81,14 +81,6 @@ class Tarot(object):
for value in map(str, range(1, 11)) + ["valet", "cavalier", "dame", "roi"]:
self.deck_ordered.append(TarotCard((suit, value)))
def createGameElt(self, to_jid, type="normal"):
type = "normal" if to_jid.resource else "groupchat"
elt = domish.Element((None, '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((None, elt_name))
@ -106,18 +98,6 @@ class Tarot(object):
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((None, 'started'))
idx = 0
for player in players:
player_elt = domish.Element((None, '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((None, 'contrat'))
@ -404,101 +384,6 @@ class Tarot(object):
yourturn_elt = mess.firstChildElement().addElement('your_turn')
self.host.profiles[profile].xmlstream.send(mess)
def userJoinedTrigger(self, room, user, profile):
"""This trigger is used to check if we are waiting people in this room,
and to create a game if everybody is here"""
_room_jid = room.occupantJID.userhostJID()
if _room_jid in self.waiting_inv and len(room.roster) == 4:
#When we have 4 people in the room, we create the game
#TODO: check people identity
players = room.roster.keys()
del self.waiting_inv[_room_jid]
self.createGame(_room_jid.userhost(), players, profile_key=profile)
return True
def launchGame(self, players, profile_key='@NONE@'):
"""Launch a game: helper method to create a room, invite players, and create the tarot game
@param players: list for players jid"""
debug(_('Launching tarot game'))
profile = self.host.memory.getProfileName(profile_key)
if not profile:
error(_("Unknown profile"))
return
def tarotRoomJoined(room):
_room = room.occupantJID.userhostJID()
for player in players:
self.host.plugins["XEP-0249"].invite(jid.JID(player), room.occupantJID.userhostJID(), {"game": "Tarot"}, profile)
self.waiting_inv[_room] = (time(), players) # TODO: remove invitation waiting for too long, using the time data
def after_init(ignore):
room_name = "sat_tarot_%s" % self.host.plugins["XEP-0045"].getUniqueName(profile_key)
print "\n\n===> room_name:", room_name
#muc_service = self.host.memory.getServerServiceEntity("conference", "text", profile)
muc_service = None
for service in self.host.memory.getServerServiceEntities("conference", "text", profile):
if not ".irc." in service.userhost():
#FIXME:
#This awfull ugly hack is here to avoid an issue with openfire: the irc gateway
#use "conference/text" identity (instead of "conference/irc"), there is certainly a better way
#to manage this, but this hack fill do it for test purpose
muc_service = service
break
if not muc_service:
error(_("Can't find a MUC service"))
return
_jid, xmlstream = self.host.getJidNStream(profile)
d = self.host.plugins["XEP-0045"].join(jid.JID("%s@%s" % (room_name, muc_service.userhost())), _jid.user, {}, profile).addCallback(tarotRoomJoined)
client = self.host.getClient(profile)
if not client:
error(_('No client for this profile key: %s') % profile_key)
return
client.client_initialized.addCallback(after_init)
def createGame(self, room_jid_param, players, profile_key='@NONE@'):
"""Create a new game
@param room_jid_param: jid of the room
@param players: list of players nick (nick must exist in the room)
@param profile_key: %(doc_profile_key)s"""
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 room_jid in self.games:
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='@NONE@'):
"""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='@NONE@'):
"""Must be call by player when the contrat is selected
@param player: player's name
@ -533,36 +418,32 @@ class Tarot(object):
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)
def newRound(self, room_jid, profile):
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
common_data = {'contrat': None,
'levees': [], # cards won
'played': None, # card on the table
'wait_for_low': None # Used when a player wait for a low card because of excuse
}
hand = game_data['hand'] = {}
hand_size = game_data['hand_size']
chien = game_data['chien'] = []
deck = self.deck_ordered[:]
random.shuffle(deck)
for i in range(4):
hand[players[i]] = deck[0:hand_size]
del deck[0:hand_size]
chien.extend(deck)
del(deck[:])
msg_elts = {}
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
msg_elts[player] = self.__card_list_to_xml(hand[player], 'hand')
RoomGame.newRound(self, room_jid, (common_data, msg_elts), profile)
pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players) # the player after the dealer start
player = players[pl_idx]
@ -572,6 +453,9 @@ class Tarot(object):
self.host.profiles[profile].xmlstream.send(mess)
def card_game_cmd(self, mess_elt, profile):
"""
@param mess_elt: instance of twisted.words.xish.domish.Element
"""
from_jid = jid.JID(mess_elt['from'])
room_jid = jid.JID(from_jid.userhost())
game_elt = mess_elt.firstChildElement()
@ -593,7 +477,7 @@ class Tarot(object):
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)
self.newRound(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)

View file

235
src/tools/plugins/games.py Normal file
View file

@ -0,0 +1,235 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# SAT: a jabber client
# Copyright (C) 2009, 2010, 2011, 2012, 2013 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 Affero 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 Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from logging import debug, warning, error
from twisted.words.protocols.jabber.jid import JID
from twisted.words.xish import domish
from time import time
"""This library help manage general games (e.g. card games) and it can be used by plugins"""
class RoomGame(object):
"""This class is used to help launching a MUC game."""
def __init__(self, host, plugin_info, ns_tag, player_init_data={}, options={}):
"""
@param host
@param name: the name of this name
@param player_init_data: dictionary for initialization data, applicable to each player
@param options: dictionary for game options
"""
self.host = host
self.name = plugin_info["import_name"]
self.ns_tag = ns_tag
self.player_init_data = player_init_data
self.collectiveGame = self.player_init_data is {}
self.options = options
self.games = {}
self.waiting_inv = {} # Invitation waiting for people to join to launch a game
# args: room_jid, referee, players, profile
host.bridge.addSignal("%sGo" % self.name, ".plugin", signature='ssass')
def prepareRoom(self, other_players, profile_key='@NONE@'):
"""Prepare the room for a game: create it and invite players.
@param other_players: list for other players JID userhosts
"""
debug(_('Preparing room for %s game') % self.name)
profile = self.host.memory.getProfileName(profile_key)
if not profile:
error(_("Unknown profile"))
return
_jid, xmlstream = self.host.getJidNStream(profile)
if other_players is None:
other_players = []
players = other_players[:]
players.append(_jid.userhost())
def roomJoined(room):
_room = room.occupantJID.userhostJID()
if self.collectiveGame is True or other_players == [] and _jid in [user.entity for user in room.roster.values()]:
self.createGame(_room.userhost(), None if self.collectiveGame is True else players, profile_key=profile)
else:
self.waiting_inv[_room] = (time(), players) # TODO: remove invitation waiting for too long, using the time data
for player in other_players:
self.host.plugins["XEP-0249"].invite(JID(player), room.occupantJID.userhostJID(), {"game": self.name}, profile)
def after_init(ignore):
room_name = "sat_%s_%s" % (self.name.lower(), self.host.plugins["XEP-0045"].getUniqueName(profile_key))
print "\n\n===> room_name:", room_name
muc_service = None
for service in self.host.memory.getServerServiceEntities("conference", "text", profile):
if not ".irc." in service.userhost():
#FIXME:
#This awfull ugly hack is here to avoid an issue with openfire: the irc gateway
#use "conference/text" identity (instead of "conference/irc"), there is certainly a better way
#to manage this, but this hack fill do it for test purpose
muc_service = service
break
if not muc_service:
error(_("Can't find a MUC service"))
return
d = self.host.plugins["XEP-0045"].join(JID("%s@%s" % (room_name, muc_service.userhost())), _jid.user, {}, profile)
d.addCallback(roomJoined)
client = self.host.getClient(profile)
if not client:
error(_('No client for this profile key: %s') % profile_key)
return
client.client_initialized.addCallback(after_init)
def userJoinedTrigger(self, room, user, profile):
"""This trigger is used to check if we are waiting for people in this room,
and to create a game if everybody is here.
@room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User}
@user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID
"""
_room_jid = room.occupantJID.userhostJID()
if self.collectiveGame is True:
if _room_jid in self.games and self.games[_room_jid]["referee"] == room.occupantJID.full():
#we are in a radiocol room, let's start the party !
mess = self.createGameElt(JID(_room_jid + '/' + user.nick))
mess.firstChildElement().addChild(self.__create_started_elt())
self.host.profiles[profile].xmlstream.send(mess)
return True
if _room_jid in self.waiting_inv and len(room.roster) >= len(self.waiting_inv[_room_jid][1]):
expected_players = self.waiting_inv[_room_jid][1]
players = []
for player in expected_players:
for user in room.roster.values():
if user.entity is not None:
# check people identity
if user.entity.userhost() == player:
players.append(user.nick)
continue
else:
# TODO: how to check the identity with only a nickname??
if user.nick == JID(player).user:
players.append(user.nick)
if len(players) < len(expected_players):
# Someone here was not invited! He can stay but he doesn't play :p
return True
# When we have all people in the room, we create the game
del self.waiting_inv[_room_jid]
self.createGame(_room_jid.userhost(), players, profile_key=profile)
return True
def createGame(self, room_jid, players=None, profile_key='@NONE@'):
"""Create a new game
@param room_jid: jid of the room
@param players: list of players nick (nick must exist in the room)
@param profile_key: %(doc_profile_key)s"""
debug(_("Creating %s game") % self.name)
room = JID(room_jid).userhost()
profile = self.host.memory.getProfileName(profile_key)
if not profile:
error(_("profile %s is unknown") % profile_key)
return
if room in self.games:
warning(_("%s game already started in room %s") % (self.name, room))
return
room_nick = self.host.plugins["XEP-0045"].getRoomNick(room, profile)
if not room_nick:
error('Internal error')
return
referee = room + '/' + room_nick
self.games[room] = {'referee': referee}
self.games[room].update(self.options)
if self.collectiveGame is True:
mess = self.createGameElt(JID(room))
mess.firstChildElement().addChild(self.__create_started_elt())
self.host.profiles[profile].xmlstream.send(mess)
return
# non collaborative game = individual data and messages
status = {}
players_data = {}
for player in players:
# The dict must be COPIED otherwise it is shared between all users
players_data[player] = self.player_init_data.copy()
status[player] = "init"
# each player send a message to all the others
mess = self.createGameElt(JID(room + '/' + player))
mess.firstChildElement().addChild(self.__create_started_elt(players))
self.host.profiles[profile].xmlstream.send(mess)
# specific data to each player
self.games[room].update({'players': players, 'status': status, 'players_data': players_data})
def createCollectiveGame(self, room_jid, profile_key='@NONE@'):
return self.createGame(self, room_jid, players=None, profile_key=profile_key)
def playerReady(self, player, referee, profile_key='@NONE@'):
"""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(referee))
ready_elt = mess.firstChildElement().addElement('player_ready')
ready_elt['player'] = player
self.host.profiles[profile].xmlstream.send(mess)
def newRound(self, room_jid, data, profile):
"""Launch a new round (reinit the user data)"""
debug(_('new round for %s game') % self.name)
game_data = self.games[room_jid.userhost()]
players = game_data['players']
players_data = game_data['players_data']
game_data['stage'] = "init"
common_data, msg_elts = data if data is not None else (None, None)
if isinstance(msg_elts, dict):
for player in players:
to_jid = JID(room_jid.userhost() + "/" + player) # FIXME: gof:
mess = self.createGameElt(to_jid)
if isinstance(msg_elts[player], domish.Element):
mess.firstChildElement().addChild(msg_elts[player])
self.host.profiles[profile].xmlstream.send(mess)
elif isinstance(msg_elts, domish.Element):
mess = self.createGameElt(room_jid)
mess.firstChildElement().addChild(msg_elts)
self.host.profiles[profile].xmlstream.send(mess)
if common_data is not None:
for player in players:
players_data[player].update(common_data)
def createGameElt(self, to_jid, type_="normal"):
"""Create a generic domish Element for the game"""
type_ = "normal" if to_jid.resource else "groupchat"
elt = domish.Element((None, 'message'))
elt["to"] = to_jid.full()
elt["type"] = type_
elt.addElement(self.ns_tag)
return elt
def __create_started_elt(self, players=None):
"""Create a game "started" domish Element"""
started_elt = domish.Element((None, 'started'))
if players is None:
return started_elt
idx = 0
for player in players:
player_elt = domish.Element((None, 'player'))
player_elt.addContent(player)
player_elt['index'] = str(idx)
idx += 1
started_elt.addChild(player_elt)
return started_elt