Index: ps/trunk/source/tools/XpartaMuPP/config.py
===================================================================
--- ps/trunk/source/tools/XpartaMuPP/config.py (revision 14433)
+++ ps/trunk/source/tools/XpartaMuPP/config.py (nonexistent)
@@ -1,22 +0,0 @@
-# Rating that new players should be inserted into the
-# database with, before they've played any games.
-default_rating = 1200
-
-# Required minimum number of games to get on the
-# leaderboard.
-leaderboard_minimum_games = 10
-
-# Required minimum number of games per month to
-# qualify as an active player.
-leaderboard_active_games = 5
-
-# Difference between two ratings such that it is
-# regarded as a "sure win" for the higher player.
-# No points are gained or lost for such a game.
-elo_sure_win_difference = 600
-
-# Lower ratings "move faster" and change more
-# dramatically than higher ones. Anything rating above
-# this value moves at the same rate as this value.
-elo_k_factor_constant_rating = 2200
-
Index: ps/trunk/source/tools/XpartaMuPP/ELO.py
===================================================================
--- ps/trunk/source/tools/XpartaMuPP/ELO.py (revision 14433)
+++ ps/trunk/source/tools/XpartaMuPP/ELO.py (revision 14434)
@@ -1,31 +1,58 @@
-from config import elo_sure_win_difference, elo_k_factor_constant_rating
+"""Copyright (C) 2013 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+"""
+############ Constants ############
+# Difference between two ratings such that it is
+# regarded as a "sure win" for the higher player.
+# No points are gained or lost for such a game.
+elo_sure_win_difference = 600
+
+# Lower ratings "move faster" and change more
+# dramatically than higher ones. Anything rating above
+# this value moves at the same rate as this value.
+elo_k_factor_constant_rating = 2200
+
+############ Functions ############
def get_rating_adjustment(rating, opponent_rating, games_played, opponent_games_played, result):
"""
Calculates the rating adjustment after a 1v1 game finishes using simplified ELO.
Arguments:
rating, opponent_rating - Ratings of the players before this game.
games_played, opponent_games_played - Number of games each player has played
before this game.
result - 1 for the first player (rating, games_played) won, 0 for draw, or
-1 for the second player (opponent_rating, opponent_games_played) won.
Returns:
The integer that should be subtracted from the loser's rating and added
to the winner's rating to get their new ratings.
TODO: Team games.
"""
opponent_volatility_influence = max(1, pow(min(games_played + 1, 50) / min(opponent_games_played + 1, 50), 0.5))
rating_k_factor = 0.75 * pow(elo_k_factor_constant_rating / min(elo_k_factor_constant_rating, (rating + opponent_rating) / 2), 0.5)
player_volatility = min(pow(1.1, games_played + 16), 25)
volatility = opponent_volatility_influence * player_volatility / rating_k_factor
difference = opponent_rating - rating
if result == 1:
return round(max(0, (difference + result * elo_sure_win_difference) / volatility))
elif result == -1:
return round(min(0, (difference + result * elo_sure_win_difference) / volatility))
else:
return round(difference / volatility)
Index: ps/trunk/source/tools/XpartaMuPP/LobbyRanking.py
===================================================================
--- ps/trunk/source/tools/XpartaMuPP/LobbyRanking.py (revision 14433)
+++ ps/trunk/source/tools/XpartaMuPP/LobbyRanking.py (revision 14434)
@@ -1,82 +1,99 @@
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""Copyright (C) 2013 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 0 A.D. If not, see .
+"""
import sqlalchemy
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = sqlalchemy.create_engine('sqlite:///lobby_rankings.sqlite3')
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
class Player(Base):
__tablename__ = 'players'
id = Column(Integer, primary_key=True)
jid = Column(String(255))
rating = Column(Integer)
games = relationship('Game', secondary='players_info')
# These two relations really only exist to satisfy the linkage
# between PlayerInfo and Player and Game and player.
games_info = relationship('PlayerInfo', backref='player')
games_won = relationship('Game', backref='winner')
class PlayerInfo(Base):
__tablename__ = 'players_info'
id = Column(Integer, primary_key=True)
player_id = Column(Integer, ForeignKey('players.id'))
game_id = Column(Integer, ForeignKey('games.id'))
civ = String(20)
economyScore = Column(Integer)
militaryScore = Column(Integer)
explorationScore = Column(Integer)
# Store to avoid needlessly recomputing it.
totalScore = Column(Integer)
unitsTrained = Column(Integer)
unitsLost = Column(Integer)
unitsKilled = Column(Integer)
buildingsConstructed = Column(Integer)
buildingsLost = Column(Integer)
buildingsDestroyed = Column(Integer)
civCentersBuilt = Column(Integer)
civCentersDestroyed = Column(Integer)
percentMapExplored = Column(Integer)
foodGathered = Column(Integer)
foodUsed = Column(Integer)
woodGathered = Column(Integer)
woodUsed = Column(Integer)
stoneGathered = Column(Integer)
stoneUsed = Column(Integer)
metalGathered = Column(Integer)
metalUsed = Column(Integer)
vegetarianRatio = Column(Integer)
treasuresCollected = Column(Integer)
tributesSent = Column(Integer)
tributesRecieved = Column(Integer)
foodBought = Column(Integer)
foodSold = Column(Integer)
woodBought = Column(Integer)
woodSold = Column(Integer)
stoneBought = Column(Integer)
stoneSold = Column(Integer)
metalBought = Column(Integer)
metalSold = Column(Integer)
barterEfficiency = Column(Integer)
tradeIncome = Column(Integer)
class Game(Base):
__tablename__ = 'games'
id = Column(Integer, primary_key=True)
map = Column(String(80))
duration = Column(Integer)
winner_id = Column(Integer, ForeignKey('players.id'))
player_info = relationship('PlayerInfo', backref='game')
players = relationship('Player', secondary='players_info')
if __name__ == '__main__':
Base.metadata.create_all(engine)
Index: ps/trunk/source/tools/XpartaMuPP/XpartaMuPP.py
===================================================================
--- ps/trunk/source/tools/XpartaMuPP/XpartaMuPP.py (revision 14433)
+++ ps/trunk/source/tools/XpartaMuPP/XpartaMuPP.py (revision 14434)
@@ -1,691 +1,693 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. 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 0 A.D. If not, see .
"""
import logging, time, traceback
from optparse import OptionParser
import sleekxmpp
from sleekxmpp.stanza import Iq
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin, ET
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from LobbyRanking import session as db, Game, Player, PlayerInfo
from ELO import get_rating_adjustment
-from config import default_rating, leaderboard_minimum_games, leaderboard_active_games
+# Rating that new players should be inserted into the
+# database with, before they've played any games.
+leaderboard_default_rating = 1200
## Class that contains and manages leaderboard data ##
class LeaderboardList():
def __init__(self, room):
self.room = room
self.lastRated = ""
def getOrCreatePlayer(self, JID):
"""
Stores a player(JID) in the database if they don't yet exist.
Returns either the newly created instance of
the Player model, or the one that already
exists in the database.
"""
players = db.query(Player).filter_by(jid=str(JID))
if not players.first():
player = Player(jid=str(JID), rating=-1)
db.add(player)
db.commit()
return player
return players.first()
def removePlayer(self, JID):
"""
Remove a player(JID) from database.
Returns the player that was removed, or None
if that player didn't exist.
"""
players = db.query(Player).filter_by(jid=JID)
player = players.first()
if not player:
return None
players.delete()
return player
def addGame(self, gamereport):
"""
Adds a game to the database and updates the data
on a player(JID) from game results.
Returns the created Game object, or None if
the creation failed for any reason.
Side effects:
Inserts a new Game instance into the database.
"""
# Discard any games still in progress.
if any(map(lambda state: state == 'active',
dict.values(gamereport['playerStates']))):
return None
players = map(lambda jid: db.query(Player).filter_by(jid=jid).first(),
dict.keys(gamereport['playerStates']))
winning_jid = list(dict.keys({jid: state for jid, state in
gamereport['playerStates'].items()
if state == 'won'}))[0]
def get(stat, jid):
return gamereport[stat][jid]
stats = {'civ': 'civs', 'foodGathered': 'foodGathered', 'foodUsed': 'foodUsed',
'woodGathered': 'woodGathered', 'woodUsed': 'woodUsed',
'stoneGathered': 'stoneGathered', 'stoneUsed': 'stoneUsed',
'metalGathered': 'metalGathered', 'metalUsed': 'metalUsed'}
playerInfos = []
for player in players:
jid = player.jid
playerinfo = PlayerInfo(player=player)
for dbname, reportname in stats.items():
setattr(playerinfo, dbname, get(reportname, jid))
playerInfos.append(playerinfo)
game = Game(map=gamereport['mapName'], duration=int(gamereport['timeElapsed']))
game.players.extend(players)
game.player_info.extend(playerInfos)
game.winner = db.query(Player).filter_by(jid=winning_jid).first()
db.add(game)
db.commit()
return game
def rateGame(self, game):
"""
Takes a game with 2 players and alters their ratings
based on the result of the game.
Returns self.
Side effects:
Changes the game's players' ratings in the database.
"""
player1 = game.players[0]
player2 = game.players[1]
# TODO: Support draws. Since it's impossible to draw in the game currently,
# the database model, and therefore this code, requires a winner.
# The Elo implementation does not, however.
result = 1 if player1 == game.winner else -1
# Player's ratings are -1 unless they have played a rated game.
if player1.rating == -1:
- player1.rating == default_rating
+ player1.rating == leaderboard_default_rating
if player2.rating == -1:
- player2.rating == default_rating
+ player2.rating == leaderboard_default_rating
rating_adjustment1 = int(get_rating_adjustment(player1.rating, player2.rating,
len(player1.games), len(player2.games), result))
rating_adjustment2 = int(get_rating_adjustment(player2.rating, player1.rating,
len(player2.games), len(player1.games), result * -1))
if result == 1:
resultQualitative = "won"
elif result == 0:
resultQualitative = "drew"
else:
resultQualitative = "lost"
name1 = '@'.join(player1.jid.split('@')[:-1])
name2 = '@'.join(player2.jid.split('@')[:-1])
self.lastRated = "A rated game has ended. %s %s against %s. Rating Adjustment: %s (%s -> %s) and %s (%s -> %s)."%(name1,
resultQualitative, name2, name1, player1.rating, player1.rating + rating_adjustment1,
name2, player2.rating, player2.rating + rating_adjustment2)
player1.rating += rating_adjustment1
player2.rating += rating_adjustment2
db.commit()
return self
def getLastRatedMessage(self):
"""
Gets the string of the last rated game. Triggers an update
chat for the bot.
"""
return self.lastRated
def addAndRateGame(self, gamereport):
"""
Calls addGame and if the game has only two
players, also calls rateGame.
Returns the result of addGame.
"""
game = self.addGame(gamereport)
if game and len(game.players) == 2:
self.rateGame(game)
else:
self.lastRated = ""
return game
def getBoard(self):
"""
Returns a dictionary of player rankings to
JIDs for sending.
"""
board = {}
players = db.query(Player).order_by(Player.rating.desc()).limit(100).all()
for rank, player in enumerate(players):
# Don't send uninitialized ratings.
if player.rating == -1:
continue
board[player.jid] = {'name': '@'.join(player.jid.split('@')[:-1]), 'rating': str(player.rating)}
return board
## Class to tracks all games in the lobby ##
class GameList():
def __init__(self):
self.gameList = {}
def addGame(self, JID, data):
"""
Add a game
"""
data['players-init'] = data['players']
data['nbp-init'] = data['nbp']
data['state'] = 'init'
self.gameList[str(JID)] = data
def removeGame(self, JID):
"""
Remove a game attached to a JID
"""
del self.gameList[str(JID)]
def getAllGames(self):
"""
Returns all games
"""
return self.gameList
def changeGameState(self, JID, data):
"""
Switch game state between running and waiting
"""
JID = str(JID)
if JID in self.gameList:
if self.gameList[JID]['nbp-init'] > data['nbp']:
logging.debug("change game (%s) state from %s to %s", JID, self.gameList[JID]['state'], 'waiting')
self.gameList[JID]['nbp'] = data['nbp']
self.gameList[JID]['state'] = 'waiting'
else:
logging.debug("change game (%s) state from %s to %s", JID, self.gameList[JID]['state'], 'running')
self.gameList[JID]['nbp'] = data['nbp']
self.gameList[JID]['state'] = 'running'
## Class which manages different game reports from clients ##
## and calls leaderboard functions as appropriate. ##
class ReportManager():
def __init__(self, leaderboard):
self.leaderboard = leaderboard
self.interimReportTracker = []
self.interimJIDTracker = []
def addReport(self, JID, rawGameReport):
"""
Adds a game to the interface between a raw report
and the leaderboard database.
"""
# cleanRawGameReport is a copy of rawGameReport with all reporter specific information removed.
cleanRawGameReport = rawGameReport.copy()
del cleanRawGameReport["playerID"]
if cleanRawGameReport not in self.interimReportTracker:
# Store the game.
appendIndex = len(self.interimReportTracker)
self.interimReportTracker.append(cleanRawGameReport)
# Initilize the JIDs and store the initial JID.
JIDs = [None] * self.getNumPlayers(rawGameReport)
JIDs[int(rawGameReport["playerID"])-1] = str(JID)
self.interimJIDTracker.append(JIDs)
else:
# We get the index at which the JIDs coresponding to the game are stored.
index = self.interimReportTracker.index(cleanRawGameReport)
# We insert the new report JID into the acending list of JIDs for the game.
JIDs = self.interimJIDTracker[index]
JIDs[int(rawGameReport["playerID"])-1] = str(JID)
self.interimJIDTracker[index] = JIDs
self.checkFull()
def expandReport(self, rawGameReport, JIDs):
"""
Takes an raw game report and re-formats it into
Python data structures leaving JIDs empty.
Returns a processed gameReport of type dict.
"""
processedGameReport = {}
for key in rawGameReport:
if rawGameReport[key].find(",") == -1:
processedGameReport[key] = rawGameReport[key]
else:
split = rawGameReport[key].split(",")
# Remove the false split positive.
split.pop()
# We just delete gaia for now.
split.pop(0)
statToJID = {}
for i, part in enumerate(split):
statToJID[JIDs[i]] = part
processedGameReport[key] = statToJID
return processedGameReport
def checkFull(self):
"""
Searches internal database to check if enough
reports have been submitted to add a game to
the leaderboard. If so, the report will be
interpolated and addAndRateGame will be
called with the result.
"""
i = 0
length = len(self.interimReportTracker)
while(i < length):
numPlayers = self.getNumPlayers(self.interimReportTracker[i])
numReports = 0
for JID in self.interimJIDTracker[i]:
if JID != None:
numReports += 1
if numReports == numPlayers:
self.leaderboard.addAndRateGame(self.expandReport(self.interimReportTracker[i], self.interimJIDTracker[i]))
del self.interimJIDTracker[i]
del self.interimReportTracker[i]
length -= 1
else:
i += 1
self.leaderboard.lastRated = ""
def getNumPlayers(self, rawGameReport):
"""
Computes the number of players in a raw gameReport.
Returns int, the number of players.
"""
# Find a key in the report which holds values for multiple players.
for key in rawGameReport:
if rawGameReport[key].find(",") != -1:
# Count the number of values, minus one for gaia and one for the false split positive.
return len(rawGameReport[key].split(","))-2
# Return -1 in case of failure.
return -1
## Class for custom gamelist stanza extension ##
class GameListXmppPlugin(ElementBase):
name = 'query'
namespace = 'jabber:iq:gamelist'
interfaces = set(('game', 'command'))
sub_interfaces = interfaces
plugin_attrib = 'gamelist'
def addGame(self, data):
itemXml = ET.Element("game", data)
self.xml.append(itemXml)
def getGame(self):
"""
Required to parse incoming stanzas with this
extension.
"""
game = self.xml.find('{%s}game' % self.namespace)
data = {}
for key, item in game.items():
data[key] = item
return data
## Class for custom boardlist stanza extension ##
class BoardListXmppPlugin(ElementBase):
name = 'query'
namespace = 'jabber:iq:boardlist'
interfaces = ('board')
sub_interfaces = interfaces
plugin_attrib = 'boardlist'
def addItem(self, name, rating):
itemXml = ET.Element("board", {"name": name, "rating": rating})
self.xml.append(itemXml)
## Class for custom gamereport stanza extension ##
class GameReportXmppPlugin(ElementBase):
name = 'report'
namespace = 'jabber:iq:gamereport'
plugin_attrib = 'gamereport'
interfaces = ('game')
sub_interfaces = interfaces
def getGame(self):
"""
Required to parse incoming stanzas with this
extension.
"""
game = self.xml.find('{%s}game' % self.namespace)
data = {}
for key, item in game.items():
data[key] = item
return data
## Main class which handles IQ data and sends new data ##
class XpartaMuPP(sleekxmpp.ClientXMPP):
"""
A simple list provider
"""
def __init__(self, sjid, password, room, nick):
sleekxmpp.ClientXMPP.__init__(self, sjid, password)
self.sjid = sjid
self.room = room
self.nick = nick
# Game collection
self.gameList = GameList()
# Init leaderboard object
self.leaderboard = LeaderboardList(room)
# gameReport to leaderboard abstraction
self.reportManager = ReportManager(self.leaderboard)
# Store mapping of nicks and XmppIDs, attached via presence stanza
self.nicks = {}
self.lastLeft = ""
register_stanza_plugin(Iq, GameListXmppPlugin)
register_stanza_plugin(Iq, BoardListXmppPlugin)
register_stanza_plugin(Iq, GameReportXmppPlugin)
self.register_handler(Callback('Iq Gamelist',
StanzaPath('iq/gamelist'),
self.iqhandler,
instream=True))
self.register_handler(Callback('Iq Boardlist',
StanzaPath('iq/boardlist'),
self.iqhandler,
instream=True))
self.register_handler(Callback('Iq GameReport',
StanzaPath('iq/gamereport'),
self.iqhandler,
instream=True))
self.add_event_handler("session_start", self.start)
self.add_event_handler("muc::%s::got_online" % self.room, self.muc_online)
self.add_event_handler("muc::%s::got_offline" % self.room, self.muc_offline)
def start(self, event):
"""
Process the session_start event
"""
self.plugin['xep_0045'].joinMUC(self.room, self.nick)
self.send_presence()
self.get_roster()
logging.info("XpartaMuPP started")
def muc_online(self, presence):
"""
Process presence stanza from a chat room.
"""
if presence['muc']['nick'] != self.nick:
# If it doesn't already exist, store player JID mapped to their nick.
if str(presence['muc']['jid']) not in self.nicks:
self.nicks[str(presence['muc']['jid'])] = presence['muc']['nick']
# Check the jid isn't already in the lobby.
if str(presence['muc']['jid']) != self.lastLeft:
self.send_message(mto=presence['from'], mbody="Hello %s, welcome to the 0 A.D. lobby. Polish your weapons and get ready to fight!" %(presence['muc']['nick']), mtype='')
# Send Gamelist to new player.
self.sendGameList(presence['muc']['jid'])
# Following two calls make sqlalchemy complain about using objects in the
# incorrect thread. TODO: Figure out how to fix this.
# Send Leaderboard to new player.
#self.sendBoardList(presence['muc']['jid'])
# Register on leaderboard.
#self.leaderboard.getOrCreatePlayer(presence['muc']['jid'])
logging.debug("Client '%s' connected with a nick of '%s'." %(presence['muc']['jid'], presence['muc']['nick']))
def muc_offline(self, presence):
"""
Process presence stanza from a chat room.
"""
# Clean up after a player leaves
if presence['muc']['nick'] != self.nick:
# Delete any games they were hosting.
for JID in self.gameList.getAllGames():
if JID == str(presence['muc']['jid']):
self.gameList.removeGame(JID)
self.sendGameList()
break
# Remove them from the local player list.
self.lastLeft = str(presence['muc']['jid'])
if str(presence['muc']['jid']) in self.nicks:
del self.nicks[str(presence['muc']['jid'])]
def iqhandler(self, iq):
"""
Handle the custom stanzas
This method should be very robust because we could receive anything
"""
if iq['type'] == 'error':
logging.error('iqhandler error' + iq['error']['condition'])
#self.disconnect()
elif iq['type'] == 'get':
"""
Request lists.
"""
# Send lists/register on leaderboard; depreciated once muc_online
# can send lists/register automatically on joining the room.
if 'gamelist' in iq.plugins:
try:
self.sendGameList(iq['from'])
except:
traceback.print_exc()
logging.error("Failed to process gamelist request from %s" % iq['from'].bare)
elif 'boardlist' in iq.plugins:
try:
self.leaderboard.getOrCreatePlayer(iq['from'])
self.sendBoardList(iq['from'])
except:
traceback.print_exc()
logging.error("Failed to process boardlist request from %s" % iq['from'].bare)
else:
logging.error("Unknown 'get' type stanza request from %s" % iq['from'].bare)
elif iq['type'] == 'result':
"""
Iq successfully received
"""
pass
elif iq['type'] == 'set':
if 'gamelist' in iq.plugins:
"""
Register-update / unregister a game
"""
command = iq['gamelist']['command']
if command == 'register':
# Add game
try:
self.gameList.addGame(iq['from'], iq['gamelist']['game'])
self.sendGameList()
except:
traceback.print_exc()
logging.error("Failed to process game registration data")
elif command == 'unregister':
# Remove game
try:
self.gameList.removeGame(iq['from'])
self.sendGameList()
except:
traceback.print_exc()
logging.error("Failed to process game unregistration data")
elif command == 'changestate':
# Change game status (waiting/running)
try:
self.gameList.changeGameState(iq['from'], iq['gamelist']['game'])
self.sendGameList()
except:
traceback.print_exc()
logging.error("Failed to process changestate data")
else:
logging.error("Failed to process command '%s' received from %s" % command, iq['from'].bare)
elif 'gamereport' in iq.plugins:
"""
Client is reporting end of game statistics
"""
try:
self.reportManager.addReport(iq['from'], iq['gamereport']['game'])
if self.leaderboard.getLastRatedMessage() != "":
self.send_message(mto=self.room, mbody=self.leaderboard.getLastRatedMessage(), mtype="groupchat",
mnick=self.nick)
self.sendBoardList()
except:
traceback.print_exc()
logging.error("Failed to update game statistics for %s" % iq['from'].bare)
else:
logging.error("Failed to process stanza type '%s' received from %s" % iq['type'], iq['from'].bare)
def sendGameList(self, to = ""):
"""
Send a massive stanza with the whole game list.
If no target is passed the gamelist is broadcasted
to all clients.
"""
games = self.gameList.getAllGames()
if to == "":
for JID in self.nicks.keys():
stz = GameListXmppPlugin()
## Pull games and add each to the stanza
for JIDs in games:
g = games[JIDs]
# Only send the games that are in the 'init' state and games
# that are in the 'waiting' state which the receiving player is in. TODO
if g['state'] == 'init' or (g['state'] == 'waiting' and self.nicks[str(JID)] in g['players-init']):
stz.addGame(g)
## Set additional IQ attributes
iq = self.Iq()
iq['type'] = 'result'
iq['to'] = JID
iq.setPayload(stz)
## Try sending the stanza
try:
iq.send(block=False, now=True)
except:
logging.error("Failed to send game list")
else:
## Check recipient exists
if str(to) not in self.nicks:
logging.error("No player with the XmPP ID '%s' known to send gamelist to." % str(to))
return
stz = GameListXmppPlugin()
## Pull games and add each to the stanza
for JIDs in games:
g = games[JIDs]
# Only send the games that are in the 'init' state and games
# that are in the 'waiting' state which the receiving player is in. TODO
if g['state'] == 'init' or (g['state'] == 'waiting' and self.nicks[str(to)] in g['players-init']):
stz.addGame(g)
## Set additional IQ attributes
iq = self.Iq()
iq['type'] = 'result'
iq['to'] = to
iq.setPayload(stz)
## Try sending the stanza
try:
iq.send(block=False, now=True)
except:
logging.error("Failed to send game list")
def sendBoardList(self, to = ""):
"""
Send the whole leaderboard list.
If no target is passed the boardlist is broadcasted
to all clients.
"""
## Pull leaderboard data and add it to the stanza
board = self.leaderboard.getBoard()
stz = BoardListXmppPlugin()
iq = self.Iq()
iq['type'] = 'result'
for i in board:
stz.addItem(board[i]['name'], board[i]['rating'])
iq.setPayload(stz)
if to == "":
for JID in self.nicks.keys():
## Set aditional IQ attributes
iq['to'] = JID
## Try sending the stanza
try:
iq.send(block=False, now=True)
except:
logging.error("Failed to send leaderboard list")
else:
## Check recipient exists
if str(to) not in self.nicks:
logging.error("No player with the XmPP ID '%s' known to send boardlist to" % str(to))
return
## Set aditional IQ attributes
iq['to'] = to
## Try sending the stanza
try:
iq.send(block=False, now=True)
except:
logging.error("Failed to send leaderboard list")
## Main Program ##
if __name__ == '__main__':
# Setup the command line arguments.
optp = OptionParser()
# Output verbosity options.
optp.add_option('-q', '--quiet', help='set logging to ERROR',
action='store_const', dest='loglevel',
const=logging.ERROR, default=logging.INFO)
optp.add_option('-d', '--debug', help='set logging to DEBUG',
action='store_const', dest='loglevel',
const=logging.DEBUG, default=logging.INFO)
optp.add_option('-v', '--verbose', help='set logging to COMM',
action='store_const', dest='loglevel',
const=5, default=logging.INFO)
# XpartaMuPP configuration options
optp.add_option('-m', '--domain', help='set xpartamupp domain',
action='store', dest='xdomain',
default="lobby.wildfiregames.com")
optp.add_option('-l', '--login', help='set xpartamupp login',
action='store', dest='xlogin',
default="xpartamupp")
optp.add_option('-p', '--password', help='set xpartamupp password',
action='store', dest='xpassword',
default="XXXXXX")
optp.add_option('-n', '--nickname', help='set xpartamupp nickname',
action='store', dest='xnickname',
default="WFGbot")
optp.add_option('-r', '--room', help='set muc room to join',
action='store', dest='xroom',
default="arena")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%m-%d-%y %H:%M:%S')
# XpartaMuPP
xmpp = XpartaMuPP(opts.xlogin+'@'+opts.xdomain+'/CC', opts.xpassword, opts.xroom+'@conference.'+opts.xdomain, opts.xnickname)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0045') # Multi-User Chat # used
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping
if xmpp.connect():
xmpp.process(threaded=False)
else:
logging.error("Unable to connect")