Mercurial > libervia-backend
diff src/tools/plugins/games.py @ 683:75e4f5e2cc65
plugins radiocol, card_game, quiz: code factorization
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 23 Oct 2013 12:45:13 +0200 |
parents | |
children | c9792d0be499 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/tools/plugins/games.py Wed Oct 23 12:45:13 2013 +0200 @@ -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