summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWolfgang Rohdewald <wolfgang@rohdewald.de>2016-08-03 07:18:41 (GMT)
committerWolfgang Rohdewald <wolfgang@rohdewald.de>2016-08-25 06:43:08 (GMT)
commit8b224462a87c18c9c02c42bd7ce7878d59eabafc (patch)
tree8e7292a1215a98e94e3dfbb65635cbe86ece293f
parentf6fe1f0ddfefdf9065b9622c6c4828745260c3a9 (diff)
Make source clean for pylint3 version 1.5.2
-rw-r--r--CMakeLists.txt3
-rw-r--r--src/animation.py18
-rw-r--r--src/background.py13
-rw-r--r--src/board.py12
-rw-r--r--src/chat.py2
-rw-r--r--src/client.py4
-rw-r--r--src/common.py12
-rw-r--r--src/config.py1
-rw-r--r--src/configdialog.py4
-rw-r--r--src/deferredutil.py17
-rw-r--r--src/dialogs.py6
-rw-r--r--src/differ.py2
-rw-r--r--src/game.py41
-rw-r--r--src/games.py2
-rw-r--r--src/guiutil.py2
-rw-r--r--src/hand.py19
-rw-r--r--src/handboard.py11
-rw-r--r--src/humanclient.py20
-rw-r--r--src/intelligence.py2
-rwxr-xr-xsrc/kajongg.py5
-rwxr-xr-xsrc/kajonggtest.py82
-rw-r--r--src/kde.py2
-rw-r--r--src/kdestub.py79
-rw-r--r--src/log.py14
-rw-r--r--src/login.py43
-rw-r--r--src/mainwindow.py49
-rw-r--r--src/meld.py25
-rw-r--r--src/message.py25
-rw-r--r--src/move.py4
-rw-r--r--src/permutations.py10
-rw-r--r--src/player.py18
-rw-r--r--src/playerlist.py2
-rw-r--r--src/predefined.py154
-rw-r--r--src/pylintrc22
-rw-r--r--src/qt.py14
-rw-r--r--src/qt4.py12
-rw-r--r--src/qt5.py8
-rw-r--r--src/query.py4
-rw-r--r--src/rule.py16
-rw-r--r--src/rulecode.py44
-rw-r--r--src/rulesetselector.py6
-rw-r--r--src/scene.py11
-rw-r--r--src/scoring.py9
-rw-r--r--src/scoringdialog.py39
-rwxr-xr-xsrc/scoringtest.py27
-rw-r--r--src/server.py1035
-rw-r--r--src/servercommon.py47
-rw-r--r--src/servertable.py895
-rw-r--r--src/setup.py12
-rw-r--r--src/sound.py23
-rw-r--r--src/tables.py2
-rw-r--r--src/tile.py3
-rw-r--r--src/tileset.py8
-rw-r--r--src/tilesetselector.py1
-rw-r--r--src/tree.py2
-rw-r--r--src/uiwall.py4
-rw-r--r--src/user.py145
-rw-r--r--src/util.py19
-rw-r--r--src/wall.py2
-rw-r--r--src/winprep.py23
60 files changed, 1639 insertions, 1497 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4384de3..fe004aa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -63,6 +63,9 @@ src/rulecode.py
src/scene.py
src/scoringdialog.py
src/scoring.py
+src/user.py
+src/servertable.py
+src/servercommon.py
src/server.py
src/sound.py
src/tables.py
diff --git a/src/animation.py b/src/animation.py
index 1879c67..bbd6abc 100644
--- a/src/animation.py
+++ b/src/animation.py
@@ -138,7 +138,7 @@ class ParallelAnimationGroup(QParallelAnimationGroup):
if self.debug or ParallelAnimationGroup.current.debug:
logDebug(u'Chaining Animation group %d to %d' %
(id(self), id(ParallelAnimationGroup.current)))
- self.doAfter = ParallelAnimationGroup.current.doAfter
+ self.doAfter = ParallelAnimationGroup.current.doAfter
ParallelAnimationGroup.current.doAfter = list()
ParallelAnimationGroup.current.deferred.addCallback(self.start)
else:
@@ -258,19 +258,19 @@ def __afterCurrentAnimationDo(callback, *args, **kwargs):
callback(None, *args, **kwargs)
-def afterQueuedAnimations(f):
+def afterQueuedAnimations(doAfter):
"""A decorator"""
- @functools.wraps(f)
+ @functools.wraps(doAfter)
def doAfterQueuedAnimations(*args, **kwargs):
+ """do this after all queued animations have finished"""
animate()
- method = types.MethodType(f, args[0])
+ method = types.MethodType(doAfter, args[0])
args = args[1:]
- if isPython3:
- assert f.__code__.co_varnames[
- 1] == 'deferredResult', f.__qualname__
- else:
- assert f.func_code.co_varnames[1] == 'deferredResult', f.__name__
+ varnames = doAfter.__code__.co_varnames if isPython3 else doAfter.func_code.co_varnames
+ assert varnames[1] in ('deferredResult', 'dummyDeferredResult'), \
+ '{} passed {} instead of deferredResult'.format(
+ doAfter.__qualname__ if isPython3 else doAfter.__name__, varnames[1])
return __afterCurrentAnimationDo(method, *args, **kwargs)
return doAfterQueuedAnimations
diff --git a/src/background.py b/src/background.py
index c596871..bc6224e 100644
--- a/src/background.py
+++ b/src/background.py
@@ -64,12 +64,12 @@ class Background(object):
"kmahjonggbackground", "*.desktop", KStandardDirs.Recursive)
# now we have a list of full paths. Use the base name minus .desktop:
# put the result into a set, avoiding duplicates
- backgrounds = set(str(x).rsplit('/')[-1].split('.')[0]
- for x in backgroundsAvailableQ)
+ backgrounds = list(set(str(x).rsplit('/')[-1].split('.')[0]
+ for x in backgroundsAvailableQ))
if 'default' in backgrounds:
# we want default to be first in list
sortedBackgrounds = ['default']
- sortedBackgrounds.extend(backgrounds - set(['default']))
+ sortedBackgrounds.extend(set(backgrounds) - set(['default']))
backgrounds = sortedBackgrounds
return [Background(x) for x in backgrounds]
@@ -89,7 +89,8 @@ class Background(object):
'\n'.join(str(x)
for x in KGlobal.dirs().resourceDirs("kmahjonggbackground"))
logException(BackgroundException(m18n(
- 'cannot find any background in the following directories, is libkmahjongg installed?') + directories))
+ 'cannot find any background in the following directories, is libkmahjongg installed?')
+ + directories))
else:
logWarning(
m18n(
@@ -123,7 +124,7 @@ class Background(object):
if self.__graphicspath.isEmpty():
logException(BackgroundException(
'cannot find kmahjongglib/backgrounds/%s for %s' %
- (graphName, self.desktopFileName)))
+ (graphName, self.desktopFileName)))
def pixmap(self, size):
"""returns a background pixmap or None for isPlain"""
@@ -141,7 +142,7 @@ class Background(object):
renderer = QSvgRenderer(self.__graphicspath)
if not renderer.isValid():
logException(BackgroundException(
- m18n('file <filename>%1</filename> contains no valid SVG', self.__graphicspath)))
+ m18n('file <filename>%1</filename> contains no valid SVG', self.__graphicspath)))
self.__pmap = QPixmap(width, height)
self.__pmap.fill(Qt.transparent)
painter = QPainter(self.__pmap)
diff --git a/src/board.py b/src/board.py
index c7fbdef..ba7fa36 100644
--- a/src/board.py
+++ b/src/board.py
@@ -474,7 +474,7 @@ class Board(QGraphicsRectItem):
newY = self.__yWidth * width + self.__yHeight * height + offsets[1]
QGraphicsRectItem.setPos(self, newX, newY)
- def showShadowsChanged(self, oldValue, newValue):
+ def showShadowsChanged(self, dummyOldValue, newValue):
"""set active lightSource"""
for uiTile in self.uiTiles:
uiTile.setClippingFlags()
@@ -514,7 +514,9 @@ class Board(QGraphicsRectItem):
lightSource = self._lightSource
if showShadows is None:
showShadows = Internal.Preferences.showShadows
- if self._tileset != tileset or self._lightSource != lightSource or Internal.Preferences.showShadows != showShadows:
+ if (self._tileset != tileset
+ or self._lightSource != lightSource
+ or Internal.Preferences.showShadows != showShadows):
self.prepareGeometryChange()
self._tileset = tileset
self._lightSource = lightSource
@@ -645,7 +647,7 @@ class CourtBoard(Board):
xScaleFactor = xAvail / xNeeded
yScaleFactor = yAvail / yNeeded
QGraphicsRectItem.setPos(self, newSceneX, newSceneY)
- Board.setScale(self, min(xScaleFactor, yScaleFactor))
+ self.setScale(min(xScaleFactor, yScaleFactor))
for uiTile in self.uiTiles:
uiTile.board.placeTile(uiTile)
@@ -734,8 +736,8 @@ class SelectorBoard(CourtBoard):
offsets = {Tile.dragon: (
3, 6, Tile.dragons), Tile.flower: (
4, 5, Tile.winds), Tile.season: (4, 0, Tile.winds),
- Tile.wind: (3, 0, Tile.winds), Tile.bamboo: (1, 0, Tile.numbers), Tile.stone: (2, 0, Tile.numbers),
- Tile.character: (0, 0, Tile.numbers)}
+ Tile.wind: (3, 0, Tile.winds), Tile.bamboo: (1, 0, Tile.numbers), Tile.stone: (2, 0, Tile.numbers),
+ Tile.character: (0, 0, Tile.numbers)}
row, baseColumn, order = offsets[uiTile.tile.lowerGroup]
column = baseColumn + order.index(uiTile.tile.value)
uiTile.dark = False
diff --git a/src/chat.py b/src/chat.py
index 11c6968..b522d33 100644
--- a/src/chat.py
+++ b/src/chat.py
@@ -87,7 +87,7 @@ class ChatModel(QAbstractTableModel):
elif role == Qt.DisplayRole and index.column() == 2:
result = toQVariant(m18n(chatLine.message))
elif role == Qt.ForegroundRole and index.column() == 2:
- palette = KApplication.palette()
+ palette = KApplication.palette() # pylint: disable=no-member
color = 'blue' if chatLine.isStatusMessage else palette.windowText(
)
result = toQVariant(QColor(color))
diff --git a/src/client.py b/src/client.py
index 1d4fa36..fd8b74a 100644
--- a/src/client.py
+++ b/src/client.py
@@ -187,7 +187,7 @@ class Client(pb.Referenceable):
def remote_newTables(self, tables):
"""update table list"""
newTables = list(ClientTable(self, *x)
- for x in tables) # pylint: disable=star-args
+ for x in tables)
self.tables.extend(newTables)
if Debug.table:
logDebug(
@@ -206,7 +206,7 @@ class Client(pb.Referenceable):
def tableChanged(self, table):
"""update table list"""
- newTable = ClientTable(self, *table) # pylint: disable=star-args
+ newTable = ClientTable(self, *table)
oldTable = self._tableById(newTable.tableid)
if oldTable:
self.tables.remove(oldTable)
diff --git a/src/common.py b/src/common.py
index 64f0af1..f26218e 100644
--- a/src/common.py
+++ b/src/common.py
@@ -27,6 +27,7 @@ import os
import logging
import logging.handlers
import socket
+import platform
try:
from sip import unwrapinstance
@@ -34,7 +35,6 @@ except ImportError:
def unwrapinstance(dummy):
"""if there is no sip, we have no Qt objects anyway"""
pass
-import platform
# pylint: disable=invalid-name
if platform.python_version_tuple()[0] == '3':
@@ -143,8 +143,8 @@ Options are: {opt}.
Options {stropt} take a string argument like {example}.
--debug=events can get suboptions like in --debug=events:Mouse:Hide
showing all event messages with 'Mouse' or 'Hide' in them""".format(
- opt=opt,
- stropt=', '.join(stringOptions), example=stringExample)
+ opt=opt,
+ stropt=', '.join(stringOptions), example=stringExample)
@staticmethod
def setOptions(args):
@@ -159,7 +159,7 @@ Options {stropt} take a string argument like {example}.
if len(parts) == 1:
value = True
else:
- value = ':'.join(parts[1:])
+ value = ':'.join(parts[1:]) # pylint: disable=redefined-variable-type
if option not in Debug.__dict__:
return '--debug: unknown option %s' % option
if not isinstance(Debug.__dict__[option], type(value)):
@@ -292,6 +292,7 @@ class __Internal(object):
except (AttributeError, socket.error):
haveDevLog = False
if not haveDevLog:
+ # pylint: disable=redefined-variable-type
handler = logging.handlers.RotatingFileHandler(
'kajongg.log', maxBytes=100000000, backupCount=10)
self.logger.addHandler(handler)
@@ -431,7 +432,8 @@ def unicodeString(s, encoding='utf-8'):
elif hasattr(s, 'decode'):
return s.decode(encoding)
else:
-# auf s5 testen mit ./kajonggtest.py --game=8001 --ruleset=DMJL --git=a46635d..23aca3b --log --clients=2 --servers=2 --count=4
+# TODO: auf s5 testen mit ./kajonggtest.py --game=8001 --ruleset=DMJL
+# --git=a46635d..23aca3b --log --clients=2 --servers=2 --count=4
return repr(s)
diff --git a/src/config.py b/src/config.py
index d9d02b9..7043e04 100644
--- a/src/config.py
+++ b/src/config.py
@@ -128,6 +128,7 @@ class SetupPreferences(KConfigSkeleton):
self.addBool('Display', 'uploadVoice', False)
def callTrigger(self, name):
+ """call registered callback for this attribute change"""
newValue = getattr(self, name)
if self.__oldValues[name] != newValue:
if Debug.preferences:
diff --git a/src/configdialog.py b/src/configdialog.py
index bd06710..4f1b52d 100644
--- a/src/configdialog.py
+++ b/src/configdialog.py
@@ -21,8 +21,6 @@ along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
-__all__ = ['ConfigDialog']
-
from log import m18n, m18nc
from common import Internal
@@ -35,6 +33,8 @@ from statesaver import StateSaver
from tilesetselector import TilesetSelector
from backgroundselector import BackgroundSelector
+__all__ = ['ConfigDialog']
+
class PlayConfigTab(QWidget):
diff --git a/src/deferredutil.py b/src/deferredutil.py
index 6a246c8..ea3af13 100644
--- a/src/deferredutil.py
+++ b/src/deferredutil.py
@@ -28,7 +28,7 @@ from twisted.internet.defer import Deferred
from log import m18nE, logInfo, logDebug, logException
from message import Message
-from common import Debug, isPython3, StrMixin, unicode, nativeString, nativeStringArgs
+from common import Debug, StrMixin, unicode, nativeString, nativeStringArgs
from move import Move
@@ -313,8 +313,9 @@ class DeferredBlock(StrMixin):
text = u'%s:' % command
answerList = []
for answer in sorted(set(x.prettyAnswer() for x in self.requests if x.deferred.command == command)):
- answerList.append((answer, list(x for x in self.requests
- if x.deferred.command == command and answer == x.prettyAnswer())))
+ answerList.append((answer, list(
+ x for x in self.requests
+ if x.deferred.command == command and answer == x.prettyAnswer())))
answerList = sorted(answerList, key=lambda x: len(x[1]))
answerTexts = []
if len(answerList) == 1:
@@ -385,9 +386,13 @@ class DeferredBlock(StrMixin):
def tell(self, about, receivers, command, **kwargs):
"""send info about player 'about' to users 'receivers'"""
- for kw in ('tile', 'tiles', 'meld', 'melds'):
- if kw in kwargs:
- kwargs[kw] = str(kwargs[kw]).encode()
+ def encodeKwargs():
+ """those values are classes like Meld, Tile etc.
+ Convert to str(python2) or bytes(python3"""
+ for keyword in ('tile', 'tiles', 'meld', 'melds'):
+ if keyword in kwargs:
+ kwargs[keyword] = str(kwargs[keyword]).encode()
+ encodeKwargs()
if about.__class__.__name__ == 'User':
about = self.playerForUser(about)
if not isinstance(receivers, list):
diff --git a/src/dialogs.py b/src/dialogs.py
index c70be0a..5c5b87e 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -93,8 +93,10 @@ class Prompt(MustChooseKDialog):
MustChooseKDialog.__init__(self)
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
self.setCaption(caption or '')
- box = KMessageBox.createKMessageBox(self, icon, msg,
- [], "", False, KMessageBox.Options(KMessageBox.NoExec | KMessageBox.AllowLink))
+ KMessageBox.createKMessageBox(
+ self, icon, msg,
+ [], "", False,
+ KMessageBox.Options(KMessageBox.NoExec | KMessageBox.AllowLink))
self.setButtons(KDialog.ButtonCode(buttons))
# buttons is either Yes/No or Ok
defaultButton = KDialog.Yes if KDialog.Yes & buttons else KDialog.Ok
diff --git a/src/differ.py b/src/differ.py
index e7fcba5..215e48a 100644
--- a/src/differ.py
+++ b/src/differ.py
@@ -155,7 +155,7 @@ class RulesetDiffer(QDialog):
Similarity is defined by the length of the diff list."""
leftRuleset = self.cbRuleset1.current
diffPairs = sorted([(len(x.diff(leftRuleset)), x)
- for x in self.rightRulesets])
+ for x in self.rightRulesets])
combo = self.cbRuleset2
with BlockSignals(combo):
combo.items = [x[1] for x in diffPairs]
diff --git a/src/game.py b/src/game.py
index 201cb9f..5b4ec12 100644
--- a/src/game.py
+++ b/src/game.py
@@ -156,7 +156,7 @@ class HandId(object):
charId = ' ' # align to the most common case
wind = (WINDS + 'X')[self.roundsFinished]
if withSeed:
- seed = self.seed
+ seed = str(self.seed)
else:
seed = ''
delim = '/' if withSeed or withAI else ''
@@ -183,7 +183,7 @@ class HandId(object):
(other.roundsFinished, other.rotated, other.notRotated))
def __ne__(self, other):
- return not self == other
+ return not self == other # pylint: disable=unneeded-not
def __lt__(self, other):
return (self.roundsFinished, self.rotated, self.notRotated) < (
@@ -436,7 +436,9 @@ class Game(object):
def sortPlayers(self):
"""sort by wind order. Place ourself at bottom (idx=0)"""
self.players.sort(key=lambda x: WINDS.index(x.wind))
- self.activePlayer = self.players[u'E']
+ self.activePlayer = self.players[u'E'] # pylint: disable=invalid-sequence-index
+ # TODO: new class Wind(str) with len==1 and __index__ 0..3 for ESWN
+ # that will make pylint happier
if Internal.scene:
if self.belongsToHumanPlayer():
while self.players[0] != self.myself:
@@ -454,7 +456,7 @@ class Game(object):
"""save starttime for this game"""
starttime = datetime.datetime.now().replace(microsecond=0).isoformat()
args = list([starttime, self.seed, int(self.autoPlay),
- self.ruleset.rulesetId])
+ self.ruleset.rulesetId])
args.extend([p.nameid for p in self.players])
args.append(self.gameid)
Query("update game set starttime=?,seed=?,autoplay=?,"
@@ -539,10 +541,10 @@ class Game(object):
"wind,points,payments, balance,rotated,notrotated) "
"VALUES(%d,%d,?,?,%d,'%s',%d,'%s','%s',%d,%d,%d,%d,%d)" %
(self.gameid, self.handctr, player.nameid,
- scoretime, int(player == self.__winner),
- WINDS[self.roundsFinished % 4], player.wind,
- player.handTotal, player.payment, player.balance,
- self.rotated, self.notRotated),
+ scoretime, int(player == self.__winner),
+ WINDS[self.roundsFinished % 4], player.wind,
+ player.handTotal, player.payment, player.balance,
+ self.rotated, self.notRotated),
(player.hand.string, manualrules))
logMessage += u'{player:<12} {hand:>4} {total:>5} {won} | '.format(
player=unicode(player)[:12], hand=player.handTotal,
@@ -632,21 +634,21 @@ class Game(object):
def loadFromDB(cls, gameid, client=None):
"""load game by game id and return a new Game instance"""
Internal.logPrefix = 'S' if Internal.isServer else 'C'
- qGameRecords = Query(
+ records = Query(
"select p0,p1,p2,p3,ruleset,seed from game where id = ?",
(gameid,)).records
- if not qGameRecords:
+ if not records:
return None
- qGameRecord = qGameRecords[0]
+ qGameRecord = records[0]
rulesetId = qGameRecord[4] or 1
ruleset = Ruleset.cached(rulesetId)
Players.load() # we want to make sure we have the current definitions
- qLastHandRecords = Query(
+ records = Query(
"select hand,rotated from score where game=? and hand="
"(select max(hand) from score where game=?)",
(gameid, gameid)).records
- if qLastHandRecords:
- qLastHandRecord = qLastHandRecords[0]
+ if records:
+ qLastHandRecord = records[0]
else:
qLastHandRecord = tuple([0, 0])
qScoreRecords = Query(
@@ -655,7 +657,7 @@ class Game(object):
(gameid, qLastHandRecord[0])).records
if not qScoreRecords:
# this should normally not happen
- qScoreRecords = tuple([
+ qScoreRecords = list([
tuple([qGameRecord[x], WINDS[x], 0, False, 'E'])
for x in range(4)])
if len(qScoreRecords) != 4:
@@ -670,7 +672,6 @@ class Game(object):
if len(set(x[4] for x in qScoreRecords)) != 1:
logError(u'game %d inconsistent: All score records for the same '
'hand must have the same prevailing wind' % gameid)
- prevailing = qScoreRecords[0][4]
players = list(tuple([x[1], Game.__getName(x[0])])
for x in qScoreRecords)
@@ -691,7 +692,7 @@ class Game(object):
player.getsPayment(record[2])
if record[3]:
game.winner = player
- game.roundsFinished = WINDS.index(prevailing)
+ game.roundsFinished = WINDS.index(qScoreRecords[0][4]) # prevailing wind
game.handctr += 1
game.notRotated += 1
game.maybeRotateWinds()
@@ -881,7 +882,7 @@ class PlayingGame(Game):
# in server.__prepareNewGame, gameid is None here
return
records = Query("select seed from game where id=?", (
- self.gameid,)).records
+ self.gameid,)).records
assert records, 'self.gameid: %s' % self.gameid
seed = records[0][0]
@@ -990,13 +991,13 @@ class PlayingGame(Game):
# and have a voice
if Debug.sound and player != self.myself:
logDebug(u'%s got voice from opponent: %s' % (
- player.name, player.voice))
+ player.name, player.voice))
else:
player.voice = Voice.locate(player.name)
if player.voice:
if Debug.sound:
logDebug(u'%s has own local voice %s' % (
- player.name, player.voice))
+ player.name, player.voice))
if player.voice:
for voice in Voice.availableVoices():
if (voice in available
diff --git a/src/games.py b/src/games.py
index 6078727..8249480 100644
--- a/src/games.py
+++ b/src/games.py
@@ -23,7 +23,7 @@ import datetime
from kde import KIcon
from dialogs import WarningYesNo
-from qt import usingQt5, Qt, toQVariant, RealQVariant, variantValue
+from qt import Qt, toQVariant, RealQVariant, variantValue
from qt import QAbstractTableModel, QDialogButtonBox, QDialog
from qt import QHBoxLayout, QVBoxLayout, QCheckBox
from qt import QItemSelectionModel, QAbstractItemView
diff --git a/src/guiutil.py b/src/guiutil.py
index c6a36ed..dd68288 100644
--- a/src/guiutil.py
+++ b/src/guiutil.py
@@ -27,8 +27,6 @@ from qt import QComboBox, QTableView, QSizePolicy, QAbstractItemView
from kde import KStandardDirs, KIcon
-from common import unicode
-
from log import m18n
diff --git a/src/hand.py b/src/hand.py
index b9f477b..d04e237 100644
--- a/src/hand.py
+++ b/src/hand.py
@@ -118,7 +118,7 @@ class Hand(object):
if Debug.hand or (Debug.mahJongg and self.lenOffset == 1):
self.debug(fmt('{callers}',
- callers=callers(exclude=['__init__'])))
+ callers=callers(exclude=['__init__'])))
Hand.indent += 1
_hideString = string
self.debug(fmt('New Hand {_hideString} {self.lenOffset}'))
@@ -426,8 +426,8 @@ class Hand(object):
parts.append('R' + ''.join(str(x) for x in sorted(rest)))
if lastSource or announcements:
parts.append('m{}{}'.format(
- self.lastSource or '.',
- ''.join(self.announcements)))
+ self.lastSource or '.',
+ ''.join(self.announcements)))
if lastTile:
parts.append('L{}{}'.format(lastTile, lastMeld if lastMeld else ''))
return ' '.join(parts).strip()
@@ -438,13 +438,14 @@ class Hand(object):
# combine all parts about hidden tiles plus the new one to one part
# because something like DrDrS8S9 plus S7 will have to be reordered
# anyway
- ns = self.newString(melds=chain(self.declaredMelds, self.bonusMelds),
+ newString = self.newString(
+ melds=chain(self.declaredMelds, self.bonusMelds),
rest=self.tilesInHand + [addTile],
lastSource=None,
lastTile=addTile,
lastMeld=None
)
- return Hand(self.player, ns, prevHand=self)
+ return Hand(self.player, newString, prevHand=self)
def __sub__(self, subtractTile):
"""returns a copy of self minus subtractTiles.
@@ -521,10 +522,6 @@ class Hand(object):
candis = ''.join(str(x) for x in sorted(cand)) # pylint: disable=unused-variable
self.debug(fmt('callingHands found {candis} for {rule}'))
candidates.extend(x.concealed for x in cand)
- # FIXME: we must differentiate between last Tile exposed or concealed.
- # example:
- # ./kajongg.py --game=7165/E4 --demo --ruleset=BMJA --playopen --debug=hand
- # sort only for reproducibility
for tile in sorted(set(candidates)):
if sum(x.exposed == tile.exposed for x in self.tiles) == 4:
continue
@@ -568,7 +565,7 @@ class Hand(object):
result = sorted(matchingMJRules, key=lambda x: -x.score.total())
if Debug.mahJongg:
self.debug(fmt('{callers} Found {matchingMJRules}',
- callers=callers()))
+ callers=callers()))
return result
def __arrangements(self):
@@ -619,7 +616,7 @@ class Hand(object):
lostHands = []
for mjRule, melds in self.__arrangements():
_ = self.newString(chain(self.melds, melds, self.bonusMelds),
- rest=None, lastMeld=None)
+ rest=None, lastMeld=None)
tryHand = Hand(self.player, _, prevHand=self)
if tryHand.won:
tryHand.mjRule = mjRule
diff --git a/src/handboard.py b/src/handboard.py
index 2698cd0..a42b5cf 100644
--- a/src/handboard.py
+++ b/src/handboard.py
@@ -129,7 +129,7 @@ class HandBoard(Board):
"""for debugging messages"""
return self.player.name
- def showShadowsChanged(self, oldValue, newValue):
+ def showShadowsChanged(self, dummyOldValue, dummyNewValue):
"""Add or remove the shadows."""
self.setPosition()
@@ -148,7 +148,7 @@ class HandBoard(Board):
self._reload(self.tileset, showShadows=show)
self.sync()
- def rearrangeMeldsChanged(self, oldValue, newValue):
+ def rearrangeMeldsChanged(self, dummyOldValue, newValue):
"""when True, concealed melds are grouped"""
self.concealedMeldDistance = (
self.exposedMeldDistance if newValue else 0.0)
@@ -201,7 +201,7 @@ class HandBoard(Board):
lowerLen = max(positions) if positions else 0
# TODO: keep them in the row they are in as long as there is room
if upperLen < lowerLen:
- bonusY = 0
+ bonusY = 0.0
tileLen = upperLen
else:
bonusY = self.lowerY
@@ -248,8 +248,9 @@ class HandBoard(Board):
# here we simply ignore existing tiles with no matches
matches = sorted(
matches, key=lambda x:
- + abs(newPosition.yoffset - x.yoffset) * 100
- + abs(newPosition.xoffset - x.xoffset))
+ + abs(newPosition.yoffset - x.yoffset) * 100 # pylint: disable=cell-var-from-loop
+ + abs(newPosition.xoffset - x.xoffset)) # pylint: disable=cell-var-from-loop
+ # pylint is too cautious here. Check with later versions.
match = matches[0]
result[match] = newPosition
oldTiles[match.tile].remove(match)
diff --git a/src/humanclient.py b/src/humanclient.py
index 74094c1..4d3a803 100644
--- a/src/humanclient.py
+++ b/src/humanclient.py
@@ -177,6 +177,7 @@ class ClientDialog(QDialog):
self.setModal(False)
self.btnHeight = 0
self.answered = False
+ self.move = None
def keyPressEvent(self, event):
"""ESC selects default answer"""
@@ -215,8 +216,7 @@ class ClientDialog(QDialog):
_, _, tileTxt = button.message.toolTip(button, uiTile.tile)
if tileTxt:
txt.append(tileTxt)
- txt = '<br><br>'.join(txt)
- uiTile.setToolTip(txt)
+ uiTile.setToolTip('<br><br>'.join(txt))
if self.client.game.activePlayer == self.client.game.myself:
Internal.scene.handSelectorChanged(
self.client.game.myself.handBoard)
@@ -379,11 +379,9 @@ class HumanClient(Client):
self.ruleset = None
self.beginQuestion = None
self.tableList = TableList(self)
- Connection(
- self).login(
- ).addCallbacks(
+ Connection(self).login().addCallbacks(
self.__loggedIn,
- self.__loginFailed)
+ self.__loginFailed)
@staticmethod
def shutdownHumanClients(exception=None):
@@ -511,7 +509,7 @@ class HumanClient(Client):
Client.remote_tableRemoved(self, tableid, message, *args)
self.__updateTableList()
if message:
- if not self.name in args or not message.endswith('has logged out'):
+ if self.name not in args or not message.endswith('has logged out'):
logWarning(m18n(message, *args))
def __receiveTables(self, tables):
@@ -534,9 +532,7 @@ class HumanClient(Client):
def gotRulesets(result):
"""the server sent us the wanted ruleset definitions"""
for ruleset in result:
- Ruleset.cached(
- ruleset).save(
- ) # make it known to the cache and save in db
+ Ruleset.cached(ruleset).save() # make it known to the cache and save in db
return tables
rulesetHashes = set(x[1] for x in tables)
needRulesets = list(
@@ -595,7 +591,7 @@ class HumanClient(Client):
table.chatWindow.receiveLine(chatLine)
def readyForGameStart(
- self, tableid, gameid, wantedGame, playerNames, shouldSave=True,
+ self, tableid, gameid, wantedGame, playerNames, shouldSave=True,
gameClass=None):
"""playerNames are in wind order ESWN"""
if gameClass is None:
@@ -918,7 +914,7 @@ class HumanClient(Client):
logWarning(
m18n(
'The connection to the server %1 broke, please try again later.',
- self.connection.url))
+ self.connection.url))
self.remote_serverDisconnects()
return succeed(None)
else:
diff --git a/src/intelligence.py b/src/intelligence.py
index 271ec42..996a660 100644
--- a/src/intelligence.py
+++ b/src/intelligence.py
@@ -396,7 +396,7 @@ class DiscardCandidates(list):
self.groupCounts[tile.lowerGroup] += 1
self.declaredGroupCounts[tile.lowerGroup] += 1
self.extend(list(TileAI(self, x)
- for x in sorted(set(self.hiddenTiles))))
+ for x in sorted(set(self.hiddenTiles))))
self.link()
@property
diff --git a/src/kajongg.py b/src/kajongg.py
index b33260c..8bb36b7 100755
--- a/src/kajongg.py
+++ b/src/kajongg.py
@@ -19,6 +19,8 @@ along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
+# pylint: disable=wrong-import-position
+
# keyboardinterrupt should simply terminate
# import signal
# signal.signal(signal.SIGINT, signal.SIG_DFL)
@@ -34,7 +36,6 @@ from util import kprint
# do not import modules using twisted before our reactor is running
-
def initRulesets():
"""exits if user only wanted to see available rulesets"""
import predefined # pylint: disable=unused-variable
@@ -184,7 +185,7 @@ if __name__ == "__main__":
if Options.csv:
if gitHead() == 'current':
Internal.logger.debug(
- 'You cannot write to %s with changes uncommitted to git' %
+ 'You cannot write to %s with changes uncommitted to git',
Options.csv)
sys.exit(2)
from mainwindow import MainWindow
diff --git a/src/kajonggtest.py b/src/kajonggtest.py
index 5308cca..c069536 100755
--- a/src/kajonggtest.py
+++ b/src/kajonggtest.py
@@ -192,7 +192,8 @@ class Server(StrMixin):
if OPTIONS.qt5:
cmd.append('--qt5')
if OPTIONS.log:
- self.process = subprocess.Popen(cmd, cwd=job.srcDir(),
+ self.process = subprocess.Popen(
+ cmd, cwd=job.srcDir(),
stdout=job.logFile, stderr=job.logFile)
else:
# reuse this server (otherwise it stops by itself)
@@ -258,16 +259,22 @@ class Job(StrMixin):
"""the path of the directory where the particular test is running"""
return os.path.join(Clone.clones[self.commitId].tmpdir, 'src')
+ def __startProcess(self, cmd):
+ """call Popen"""
+ print('starting %s in %s' % (self, self.server.clone.tmpdir))
+ if OPTIONS.log:
+ self.process = subprocess.Popen(
+ cmd, cwd=self.srcDir(),
+ stdout=self.logFile, stderr=self.logFile)
+ else:
+ self.process = subprocess.Popen(cmd, cwd=self.srcDir())
+
def start(self):
"""start this job"""
self.server = Server(self)
# never login to the same server twice at the
# same time with the same player name
player = self.server.jobs.index(self) + 1
- if OPTIONS.usePort:
- socketArg = '--port={sock}'.format(sock=self.server.socketName)
- else:
- socketArg = '--socket={sock}'.format(sock=self.server.socketName)
cmd = [os.path.join(self.srcDir(), 'kajongg.py'),
'--game={game}'.format(game=self.game),
'--player={tester} {player}'.format(
@@ -295,12 +302,7 @@ class Job(StrMixin):
cmd.append('--playopen')
if OPTIONS.debug:
cmd.append('--debug={dbg}'.format(dbg=','.join(OPTIONS.debug)))
- print('starting %s in %s' % (self, self.server.clone.tmpdir))
- if OPTIONS.log:
- self.process = subprocess.Popen(cmd, cwd=self.srcDir(),
- stdout=self.logFile, stderr=self.logFile)
- else:
- self.process = subprocess.Popen(cmd, cwd=self.srcDir())
+ self.__startProcess(cmd)
self.started = True
def check(self, silent=False):
@@ -323,7 +325,7 @@ class Job(StrMixin):
if self.__logFile is None:
logDir = os.path.expanduser(
os.path.join('~', '.kajongg', 'log', str(self.game),
- self.ruleset, self.aiVariant, '3' if isPython3 else '2'))
+ self.ruleset, self.aiVariant, '3' if isPython3 else '2'))
if not os.path.exists(logDir):
os.makedirs(logDir)
logFileName = self.commitId
@@ -393,8 +395,8 @@ def removeInvalidCommits(csvFile):
csvCommits = set(
x for x in _ if set(
x) <= set(
- '0123456789abcdef') and len(
- x) >= 7)
+ '0123456789abcdef') and len(
+ x) >= 7)
nonExisting = set(csvCommits) - set(onlyExistingCommits(csvCommits))
if nonExisting:
print(
@@ -420,11 +422,11 @@ def readGames(csvFile):
"""returns a dict holding a frozenset of games for each variant"""
if not os.path.exists(csvFile):
return
- allRows = neutralize(Csv.reader(csvFile))
- if not allRows:
+ allRowsGenerator = neutralize(Csv.reader(csvFile))
+ if not allRowsGenerator:
return
# we want unique tuples so we can work with sets
- allRows = set(tuple(x) for x in allRows)
+ allRows = set(tuple(x) for x in allRowsGenerator)
games = dict()
# build set of rows for every ai
for variant in set(tuple(x[:COMMITFIELD]) for x in allRows):
@@ -588,7 +590,8 @@ def doJobs():
def parse_options():
"""parse options"""
parser = OptionParser()
- parser.add_option('', '--gui', dest='gui', action='store_true',
+ parser.add_option(
+ '', '--gui', dest='gui', action='store_true',
default=False, help='show graphical user interface')
parser.add_option(
'', '--23', dest='py23', action='store_true',
@@ -597,36 +600,47 @@ def parse_options():
parser.add_option(
'', '--qt5', dest='qt5', action='store_true',
default=False, help='Force using Qt5')
- parser.add_option('', '--ruleset', dest='rulesets', default='ALL',
+ parser.add_option(
+ '', '--ruleset', dest='rulesets', default='ALL',
help='play like a robot using RULESET: comma separated list. If missing, test all rulesets',
metavar='RULESET')
- parser.add_option('', '--rounds', dest='rounds',
+ parser.add_option(
+ '', '--rounds', dest='rounds',
help='play only # ROUNDS per game',
metavar='ROUNDS')
- parser.add_option('', '--ai', dest='aiVariants',
+ parser.add_option(
+ '', '--ai', dest='aiVariants',
default=None, help='use AI variants: comma separated list',
metavar='AI')
- parser.add_option('', '--log', dest='log', action='store_true',
+ parser.add_option(
+ '', '--log', dest='log', action='store_true',
default=False, help='write detailled debug info to ~/.kajongg/log/game/ruleset/commit.'
- ' This starts a separate server process per job, it sets --servers to --clients.')
- parser.add_option('', '--game', dest='game',
- help='start first game with GAMEID, increment for following games.'
- ' Without this, random values are used.',
+ ' This starts a separate server process per job, it sets --servers to --clients.')
+ parser.add_option(
+ '', '--game', dest='game',
+ help='start first game with GAMEID, increment for following games.' +
+ ' Without this, random values are used.',
metavar='GAMEID', type=int, default=0)
- parser.add_option('', '--count', dest='count',
+ parser.add_option(
+ '', '--count', dest='count',
help='play COUNT games. Default is unlimited',
metavar='COUNT', type=int, default=999999999)
- parser.add_option('', '--playopen', dest='playopen', action='store_true',
+ parser.add_option(
+ '', '--playopen', dest='playopen', action='store_true',
help='all robots play with visible concealed tiles', default=False)
- parser.add_option('', '--clients', dest='clients',
+ parser.add_option(
+ '', '--clients', dest='clients',
help='start a maximum of CLIENTS kajongg instances. Default is 2',
metavar='CLIENTS', type=int, default=1)
- parser.add_option('', '--servers', dest='servers',
+ parser.add_option(
+ '', '--servers', dest='servers',
help='start a maximum of SERVERS kajonggserver instances. Default is 1',
metavar='SERVERS', type=int, default=1)
- parser.add_option('', '--git', dest='git',
+ parser.add_option(
+ '', '--git', dest='git',
help='check all commits: either a comma separated list or a range from..until')
- parser.add_option('', '--debug', dest='debug',
+ parser.add_option(
+ '', '--debug', dest='debug',
help=Debug.help())
return parser.parse_args()
@@ -671,13 +685,13 @@ def improve_options():
OPTIONS.rulesets = usingRulesets
if OPTIONS.git is not None:
if '..' in OPTIONS.git:
- if not '^' in OPTIONS.git:
+ if '^' not in OPTIONS.git:
OPTIONS.git = OPTIONS.git.replace('..', '^..')
commits = subprocess.check_output(
'git log --pretty=%h {range}'.format(
range=OPTIONS.git).split()).decode()
OPTIONS.git = list(reversed(list(x.strip()
- for x in commits.split('\n') if x.strip())))
+ for x in commits.split('\n') if x.strip())))
else:
OPTIONS.git = onlyExistingCommits(OPTIONS.git.split(','))
if not OPTIONS.git:
diff --git a/src/kde.py b/src/kde.py
index 3b3bc11..0a15a7f 100644
--- a/src/kde.py
+++ b/src/kde.py
@@ -18,7 +18,7 @@ along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
-# pylint: disable=unused-import
+# pylint: disable=unused-import, wrong-import-order, wrong-import-position
import os
import sys
diff --git a/src/kdestub.py b/src/kdestub.py
index acadf26..d80a27f 100644
--- a/src/kdestub.py
+++ b/src/kdestub.py
@@ -24,12 +24,6 @@ python interface to KDE.
"""
-__all__ = ['KAboutData', 'KApplication', 'KCmdLineArgs', 'KConfig',
- 'KCmdLineOptions', 'i18n', 'i18nc', 'ki18n',
- 'KMessageBox', 'KConfigSkeleton', 'KDialogButtonBox',
- 'KConfigDialog', 'KDialog', 'KLineEdit',
- 'KUser', 'KToggleFullScreenAction', 'KStandardAction',
- 'KXmlGuiWindow', 'KStandardDirs', 'KGlobal', 'KIcon', 'KAction']
import sys
import os
@@ -42,9 +36,13 @@ if os.name != 'nt':
import weakref
from collections import defaultdict
from argparse import ArgumentParser
+from locale import _parse_localename, getdefaultlocale, setlocale, LC_ALL
+
import sip
+# pylint: disable=wrong-import-order
+
try:
from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError
except ImportError:
@@ -52,20 +50,26 @@ except ImportError:
# pylint: disable=import-error
from configparser import SafeConfigParser, NoSectionError, NoOptionError
-from locale import _parse_localename, getdefaultlocale, setlocale, LC_ALL
-
# here come the replacements:
# pylint: disable=wildcard-import,unused-wildcard-import
from qt import *
-from common import Internal, Debug, ENGLISHDICT, unicodeString
+from common import Internal, Debug, ENGLISHDICT, unicodeString, isPython3
from util import uniqueList
from statesaver import StateSaver
import gettext
+__all__ = ['KAboutData', 'KApplication', 'KCmdLineArgs', 'KConfig',
+ 'KCmdLineOptions', 'i18n', 'i18nc', 'ki18n',
+ 'KMessageBox', 'KConfigSkeleton', 'KDialogButtonBox',
+ 'KConfigDialog', 'KDialog', 'KLineEdit',
+ 'KUser', 'KToggleFullScreenAction', 'KStandardAction',
+ 'KXmlGuiWindow', 'KStandardDirs', 'KGlobal', 'KIcon', 'KAction']
+
+
def __insertArgs(translatedTemplate, *args):
"""
put format arguments into the translated template.
@@ -80,7 +84,7 @@ def __insertArgs(translatedTemplate, *args):
@rtype: C{str}
"""
if '@' in translatedTemplate:
- Internal.logger.debug('insertargs:%s' % translatedTemplate)
+ Internal.logger.debug('insertargs:%s', translatedTemplate)
if '\004' in translatedTemplate:
translatedTemplate = translatedTemplate.split('\004')[1]
@@ -625,10 +629,10 @@ class KXmlGuiWindow(CaptionMixin, QMainWindow):
self.statusBar().setObjectName('StatusBar')
self.menus = {}
for menu in (
- (i18n('&Game'), ('scoreGame', 'play', 'abort', 'quit')),
- (i18n('&View'), ('scoreTable', 'explain')),
- (i18n('&Settings'), ('players', 'rulesets', 'demoMode', '', 'options_show_statusbar',
- 'options_show_toolbar', '', 'options_configure_toolbars', 'options_configure')),
+ (i18n('&Game'), ('scoreGame', 'play', 'abort', 'quit')),
+ (i18n('&View'), ('scoreTable', 'explain')),
+ (i18n('&Settings'), ('players', 'rulesets', 'demoMode', '', 'options_show_statusbar',
+ 'options_show_toolbar', '', 'options_configure_toolbars', 'options_configure')),
(i18n('&Help'), ('help', 'aboutkajongg'))):
self.menus[menu[0]] = (QMenu(menu[0]), menu[1])
self.menuBar().addMenu(self.menus[menu[0]][0])
@@ -781,9 +785,9 @@ class KStandardDirs(object):
exists = os.path.exists(tryThis)
if Debug.locate:
if exists:
- Internal.logger.debug('found: %s' % tryThis)
+ Internal.logger.debug('found: %s', tryThis)
else:
- Internal.logger.debug('not found: %s' % tryThis)
+ Internal.logger.debug('not found: %s', tryThis)
return exists, tryThis
@classmethod
@@ -812,8 +816,9 @@ class KStandardDirs(object):
fullPath)
os.makedirs(os.path.dirname(fullPath))
if Debug.locate:
- Internal.logger.debug('locateLocal(%s, %s) returns %s' % (
- type_, filename, fullPath))
+ Internal.logger.debug(
+ 'locateLocal(%s, %s) returns %s',
+ type_, filename, fullPath)
return fullPath
@classmethod
@@ -870,8 +875,8 @@ class KStandardDirs(object):
result = cls.findDirs(type_, filename)
if Debug.locate:
Internal.logger.debug(
- 'findResourceDir(%s,%s) finds %s' %
- (type_, filename, result))
+ 'findResourceDir(%s,%s) finds %s',
+ type_, filename, result)
return result
@@ -1074,8 +1079,12 @@ class KConfig(SafeConfigParser):
path = KGlobal.dirs().locateLocal("config", "kajonggrc")
self.path = str(path)
if os.path.exists(self.path):
- with codecs.open(self.path, 'rb', encoding='utf-8') as cfgFile:
- self.readfp(cfgFile)
+ with codecs.open(self.path, 'r', encoding='utf-8') as cfgFile:
+ # TODO: changed from 'rb' to 'r', test this!
+ if isPython3:
+ self.read_file(cfgFile)
+ else:
+ self.readfp(cfgFile) # pylint: disable=deprecated-method
def as_dict(self):
"""a dict of dicts"""
@@ -1105,7 +1114,7 @@ class KConfig(SafeConfigParser):
for (key, value) in self._sections[section].items():
key = bytes(str(key).encode('utf-8')) # pylint bug, bytes() should not be needed
value = str(value).encode('utf-8')
- if key == "__name__":
+ if key == b"__name__":
continue
if value is not None:
key = b"=".join((key, value.replace(b'\n', b'\n\t')))
@@ -1140,7 +1149,7 @@ class KIcon(QIcon):
name,
extension))
if not os.path.exists(name):
- Internal.logger.debug('not found:%s' % name)
+ Internal.logger.debug('not found:%s', name)
raise UserWarning('not found:%s' % name)
QIcon.__init__(self, name)
return
@@ -1353,8 +1362,8 @@ class AboutKajonggDialog(KDialog):
stdout=subprocess.PIPE).communicate()[0]
versions = versions.decode().split('\n')
versions = (x.strip() for x in versions if ': ' in x.strip())
- versions = dict(x.split(': ') for x in versions)
- underVersions.append('KDE %s' % versions['KDE'])
+ versionsDict = dict(x.split(': ') for x in versions)
+ underVersions.append('KDE %s' % versionsDict['KDE'])
except OSError:
underVersions.append(i18n('KDE (not installed)'))
underVersions.append('Qt %s' % QT_VERSION_STR)
@@ -1474,12 +1483,12 @@ class KConfigDialog(KDialog):
dialog = None
getFunc = {
'QCheckBox': 'isChecked',
- 'QSlider': 'value',
- 'QLineEdit': 'text'}
+ 'QSlider': 'value',
+ 'QLineEdit': 'text'}
setFunc = {
'QCheckBox': 'setChecked',
- 'QSlider': 'setValue',
- 'QLineEdit': 'setText'}
+ 'QSlider': 'setValue',
+ 'QLineEdit': 'setText'}
def __init__(self, parent, name, preferences):
KDialog.__init__(self, parent)
@@ -1574,14 +1583,14 @@ class KConfigDialog(KDialog):
def applySettings(self):
"""Apply pressed"""
- changed = self.updateButtons()
- self.updateSettings()
- self.updateButtons()
+ if self.updateButtons():
+ self.updateSettings()
+ self.updateButtons()
def accept(self):
"""OK pressed"""
- changed = self.updateButtons()
- self.updateSettings()
+ if self.updateButtons():
+ self.updateSettings()
KDialog.accept(self)
def updateButtons(self):
diff --git a/src/log.py b/src/log.py
index a1bdf2a..8b2a3c9 100644
--- a/src/log.py
+++ b/src/log.py
@@ -25,8 +25,6 @@ import string
from locale import getpreferredencoding
from sys import _getframe
-SERVERMARK = '&&SERVER&&'
-
# util must not import twisted or we need to change kajongg.py
from common import Internal, Debug, unicode, isPython3, ENGLISHDICT # pylint: disable=redefined-builtin
@@ -37,6 +35,9 @@ from kde import i18n, i18nc
from dialogs import Sorry, Information, NoPrompt
+SERVERMARK = '&&SERVER&&'
+
+
class Fmt(string.Formatter):
"""this formatter can parse {id(x)} and output a short ascii form for id"""
@@ -72,6 +73,7 @@ class Fmt(string.Formatter):
Fmt.formatter = Fmt()
def id4(obj):
+ """object id for debug messages"""
return '.' if Debug.neutral else Fmt.num_encode(id(obj))
def fmt(text, **kwargs):
@@ -80,11 +82,11 @@ def fmt(text, **kwargs):
if '}' in text:
parts = []
for part in text.split('}'):
- if not '{' in part:
+ if '{' not in part:
parts.append(part)
else:
part2 = part.split('{')
- if part2[1] in ('callers'):
+ if part2[1] == 'callers':
if part2[0]:
parts.append('%s:{%s}' % (part2[0], part2[1]))
else:
@@ -103,7 +105,7 @@ def fmt(text, **kwargs):
# formatter.format will not accept 'self' as keyword
argdict['SELF'] = argdict['self']
del argdict['self']
- return Fmt.formatter.format(text, **argdict) # pylint: disable=star-args
+ return Fmt.formatter.format(text, **argdict)
def translateServerMessage(msg):
@@ -200,7 +202,7 @@ def logMessage(msg, prio, showDialog, showStack=False, withGamePrefix=True):
else:
lower = -showStack - 3
for line in traceback.format_stack()[lower:-3]:
- if not 'logException' in line:
+ if 'logException' not in line:
__logUnicodeMessage(prio, ' ' + line.strip())
if showDialog and not Internal.isServer:
if prio == logging.INFO:
diff --git a/src/login.py b/src/login.py
index 55711c7..c8ff60c 100644
--- a/src/login.py
+++ b/src/login.py
@@ -29,7 +29,7 @@ from twisted.spread import pb
from twisted.cred import credentials
from twisted.internet.defer import CancelledError
from twisted.internet.task import deferLater
-from twisted.internet.error import ConnectionRefusedError, TimeoutError, ConnectionLost, DNSLookupError, ConnectError
+import twisted.internet.error
from twisted.python.failure import Failure
from qt import QDialog, QDialogButtonBox, QVBoxLayout, \
@@ -76,14 +76,14 @@ class Url(unicode):
# TODO: but host is None, do we ever get here?
url = host
if port:
- url += ':{}'.format(self.port)
+ url += ':{}'.format(port)
obj = unicode.__new__(cls, url)
obj.host = host
obj.port = port
if Options.port:
obj.port = int(Options.port)
if obj.port is None and obj.isLocalHost and not obj.useSocket:
- obj.port = obj.__findFreePort()
+ obj.port = obj.findFreePort()
if obj.port is None and not obj.isLocalHost:
obj.port = Internal.defaultPort
if Debug.connections:
@@ -99,8 +99,6 @@ class Url(unicode):
logDebug(u'Installed qt4reactor')
return obj
- return obj
-
def __repr__(self):
"""show all info"""
if self.useSocket:
@@ -126,7 +124,7 @@ class Url(unicode):
"""do server and client run on the same host?"""
return self.host in ('127.0.0.1', 'localhost')
- def __findFreePort(self):
+ def findFreePort(self):
"""find an unused port on the current system.
used when we want to start a local server on windows"""
assert self.isLocalHost
@@ -161,15 +159,16 @@ class Url(unicode):
# riverbank.computing says module dbus is deprecated
# for Python 3. And Ubuntu has no package with
# QtDBus. So we use good old subprocess.
- stdoutdata, stderrdata = subprocess.Popen(['qdbus',
- 'org.kde.kded',
- '/modules/networkstatus',
- 'org.kde.Solid.Networking.status'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+ stdoutdata, stderrdata = subprocess.Popen(
+ ['qdbus',
+ 'org.kde.kded',
+ '/modules/networkstatus',
+ 'org.kde.Solid.Networking.status'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
stdoutdata = stdoutdata.strip()
stderrdata = stderrdata.strip()
if stderrdata == '' and stdoutdata != '4':
# pylint: disable=nonstandard-exception
- raise ConnectError()
+ raise twisted.internet.error.ConnectError()
# if we have stderrdata, qdbus probably does not provide the
# service we want, so ignore it
return result
@@ -570,12 +569,10 @@ class Connection(object):
Players.createIfUnknown(self.username)
playerId = Players.allIds[self.username]
with Internal.db:
- if Query(
- 'update passwords set password=? where url=? and player=?',
+ if Query('update passwords set password=? where url=? and player=?',
(self.password, self.url, playerId)).rowcount() == 0:
- Query(
- 'insert into passwords(url,player,password) values(?,?,?)',
- (self.url, playerId, self.password))
+ Query('insert into passwords(url,player,password) values(?,?,?)',
+ (self.url, playerId, self.password))
def __checkExistingConnections(self, dummy=None):
"""do we already have a connection to the wanted URL?"""
@@ -638,24 +635,24 @@ class Connection(object):
raise CancelledError
if failure.check(CancelledError):
pass
- elif failure.check(TimeoutError):
+ elif failure.check(twisted.internet.error.TimeoutError):
msg = m18n('Server %1 did not answer', self.url)
- elif failure.check(ConnectionRefusedError):
+ elif failure.check(twisted.internet.error.ConnectionRefusedError):
msg = m18n('Server %1 refused connection', self.url)
- elif failure.check(ConnectionLost):
+ elif failure.check(twisted.internet.error.ConnectionLost):
msg = m18n('Server %1 does not run a kajongg server', self.url)
- elif failure.check(DNSLookupError):
+ elif failure.check(twisted.internet.error.DNSLookupError):
msg = m18n('Address for server %1 cannot be found', self.url)
- elif failure.check(ConnectError):
+ elif failure.check(twisted.internet.error.ConnectError):
msg = m18n(
'Login to server %1 failed: You have no network connection',
self.url)
else:
- tb = unicodeString(failure.getTraceback())
+ tracebackString = unicodeString(failure.getTraceback())
msg = u'Login to server {} failed: {}/{} Callstack:{}'.format(
self.url, failure.value.__class__.__name__, failure.getErrorMessage(
),
- tb)
+ tracebackString)
# Maybe the server is running but something is wrong with it
if self.url.useSocket:
if removeIfExists(socketName()):
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 6f3e51d..f7661c6 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -18,18 +18,21 @@ along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
+# pylint: disable=wrong-import-order, wrong-import-position
+
import sys
import os
import codecs
+from itertools import chain
-from log import logError, logDebug, m18n, m18nc
-from common import Options, Internal, isAlive, Debug
import cgitb
import tempfile
import webbrowser
import logging
-from itertools import chain
+from signal import signal, SIGABRT, SIGINT, SIGTERM
+from log import logError, logDebug, m18n, m18nc
+from common import Options, Internal, isAlive, Debug
class MyHook(cgitb.Hook):
@@ -78,7 +81,7 @@ from scoring import scoreGame
from scoringdialog import ScoreTable, ExplainView
from humanclient import HumanClient
from rulesetselector import RulesetSelector
-from animation import animate, afterQueuedAnimations, MoveImmediate
+from animation import afterQueuedAnimations, MoveImmediate
from chat import ChatWindow
from scene import PlayingScene, ScoringScene
from configdialog import ConfigDialog
@@ -108,7 +111,6 @@ def cleanExit(*dummyArgs):
# sys.exit(0)
MainWindow.aboutToQuit()
-from signal import signal, SIGABRT, SIGINT, SIGTERM
signal(SIGABRT, cleanExit)
signal(SIGINT, cleanExit)
signal(SIGTERM, cleanExit)
@@ -413,18 +415,21 @@ class MainWindow(KXmlGuiWindow):
def queryExit(self):
"""see queryClose"""
- if self.exitReady:
+ def quitDebug(*args, **kwargs):
+ """reducing branches in queryExit"""
if Debug.quit:
- logDebug(
- u'MainWindow.queryExit returns True because exitReady is set')
+ logDebug(*args, **kwargs)
+
+ if self.exitReady:
+ quitDebug(u'MainWindow.queryExit returns True because exitReady is set')
return True
if self.exitConfirmed:
# now we can get serious
self.exitReady = False
for widget in chain(
- (x.tableList for x in HumanClient.humanClients), [
- self.confDialog,
- self.rulesetWindow, self.playerWindow]):
+ (x.tableList for x in HumanClient.humanClients), [
+ self.confDialog,
+ self.rulesetWindow, self.playerWindow]):
if isAlive(widget):
widget.hide()
if self.exitWaitTime is None:
@@ -436,21 +441,17 @@ class MainWindow(KXmlGuiWindow):
u'waiting since %d seconds for reactor to stop' %
(self.exitWaitTime // 1000))
try:
- if Debug.quit:
- logDebug(u'now stopping reactor')
+ quitDebug(u'now stopping reactor')
Internal.reactor.stop()
assert isAlive(self)
QTimer.singleShot(10, self.close)
except ReactorNotRunning:
self.exitReady = True
- if Debug.quit:
- logDebug(
- u'MainWindow.queryExit returns True: It got exception ReactorNotRunning')
+ quitDebug(
+ u'MainWindow.queryExit returns True: It got exception ReactorNotRunning')
else:
self.exitReady = True
- if Debug.quit:
- logDebug(
- u'MainWindow.queryExit returns True: Reactor is not running')
+ quitDebug(u'MainWindow.queryExit returns True: Reactor is not running')
return bool(self.exitReady)
@staticmethod
@@ -583,7 +584,8 @@ class MainWindow(KXmlGuiWindow):
view.fitInView(scene.itemsBoundingRect(), Qt.KeepAspectRatio)
@afterQueuedAnimations
- def backgroundChanged(self, deferredResult, oldName, newName):
+ def backgroundChanged(self, dummyDeferredResult, dummyOldName, newName):
+ """if the wanted background changed, apply the change now"""
centralWidget = self.centralWidget()
if centralWidget:
self.background = Background(newName)
@@ -592,7 +594,8 @@ class MainWindow(KXmlGuiWindow):
@afterQueuedAnimations
def tilesetNameChanged(
- self, deferredResult, oldValue=None, newValue=None, *args):
+ self, dummyDeferredResult, dummyOldValue=None, dummyNewValue=None, *dummyArgs):
+ """if the wanted tileset changed, apply the change now"""
if self.centralView:
with MoveImmediate():
if self.scene:
@@ -600,7 +603,7 @@ class MainWindow(KXmlGuiWindow):
self.adjustView()
@afterQueuedAnimations
- def showSettings(self, deferredResult, dummyChecked=None):
+ def showSettings(self, dummyDeferredResult, dummyChecked=None):
"""show preferences dialog. If it already is visible, do nothing"""
# This is called by the triggered() signal. So why does KDE
# not return the bool checked?
@@ -654,7 +657,7 @@ class MainWindow(KXmlGuiWindow):
scene.updateSceneGUI()
@afterQueuedAnimations
- def changeAngle(self, deferredResult, dummyButtons=None, dummyModifiers=None):
+ def changeAngle(self, deferredResult, dummyButtons=None, dummyModifiers=None): # pylint: disable=unused-argument
"""change the lightSource"""
if self.scene:
with MoveImmediate():
diff --git a/src/meld.py b/src/meld.py
index 04b7433..b8d49c3 100644
--- a/src/meld.py
+++ b/src/meld.py
@@ -78,7 +78,7 @@ class Meld(TileList):
assert value.key == 1 + value.hashTable.index(value) / 2
assert value.key == TileList.key(value), \
'static key:%s current key:%s, static value:%s, current value:%s ' % (
- value.key, TileList.key(value), value.original, value)
+ value.key, TileList.key(value), value.original, value)
def __init__(self, newContent=None):
"""init the meld: content can be either
@@ -138,18 +138,20 @@ class Meld(TileList):
self,
'concealed',
Meld(TileList(x.concealed for x in self)))
- TileList.__setattr__(self, 'declared',
- Meld(TileList([self[0].exposed, self[1].concealed, self[2].concealed, self[3].exposed])))
+ TileList.__setattr__(
+ self, 'declared',
+ Meld(TileList([self[0].exposed, self[1].concealed, self[2].concealed, self[3].exposed])))
TileList.__setattr__(
self,
'exposed',
Meld(TileList(x.exposed for x in self)))
- TileList.__setattr__(self, 'exposedClaimed',
- Meld(TileList([self[0].exposed, self[1].exposed, self[2].exposed, self[3].concealed])))
+ TileList.__setattr__(
+ self, 'exposedClaimed',
+ Meld(TileList([self[0].exposed, self[1].exposed, self[2].exposed, self[3].concealed])))
def __setattr__(self, name, value):
if (hasattr(self, '_fixed')
- and not name.endswith('__hasRules')
+ and not name.endswith('__hasRules')
and not name.endswith('__hasDoublingRules')):
raise TypeError
TileList.__setattr__(self, name, value)
@@ -164,8 +166,9 @@ class Meld(TileList):
self.__hasRules = any(len(x) for x in chain(
self.__staticRules.values(), self.__dynamicRules.values()))
- self.__staticDoublingRules[rulesetId] = list(x for x in ruleset.doublingMeldRules
- if not hasattr(x, 'mayApplyToMeld') and x.appliesToMeld(None, self))
+ self.__staticDoublingRules[rulesetId] = list(
+ x for x in ruleset.doublingMeldRules
+ if not hasattr(x, 'mayApplyToMeld') and x.appliesToMeld(None, self))
self.__dynamicDoublingRules[rulesetId] = list(x for x in ruleset.doublingMeldRules
if hasattr(x, 'mayApplyToMeld') and x.mayApplyToMeld(self))
self.__hasDoublingRules = any(len(x) for x in chain(
@@ -181,7 +184,7 @@ class Meld(TileList):
self.__prepareRules(ruleset)
result = self.__staticRules[rulesetId][:]
result.extend(x for x in self.__dynamicRules[
- rulesetId] if x.appliesToMeld(hand, self))
+ rulesetId] if x.appliesToMeld(hand, self))
return result
def doublingRules(self, hand):
@@ -192,7 +195,7 @@ class Meld(TileList):
self.__prepareRules(ruleset)
result = self.__staticDoublingRules[rulesetId][:]
result.extend(x for x in self.__dynamicDoublingRules[
- rulesetId] if x.appliesToMeld(hand, self))
+ rulesetId] if x.appliesToMeld(hand, self))
return result
def append(self, dummy):
@@ -409,7 +412,7 @@ class MeldList(list):
list.append(self, newContent)
elif isinstance(newContent, str):
list.extend(self, [Meld(x)
- for x in newContent.split()]) # pylint: disable=maybe-no-member
+ for x in newContent.split()]) # pylint: disable=maybe-no-member
else:
list.extend(self, [Meld(x) for x in newContent])
self.sort()
diff --git a/src/message.py b/src/message.py
index 716908a..9be3d5c 100644
--- a/src/message.py
+++ b/src/message.py
@@ -21,11 +21,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import datetime
from log import m18n, m18nc, m18ncE, logWarning, logException, logDebug
-from sound import Voice, Sound
+from sound import Voice
from tile import Tile, TileList
from meld import Meld, MeldList
from common import Internal, Debug, Options, long
-from common import unicode, unicodeString, StrMixin
+from common import unicode, unicodeString
from dialogs import Sorry
# pylint: disable=super-init-not-called
@@ -213,13 +213,13 @@ class PungChowMessage(NotifyAtOnceMessage):
dangerousMelds)
if len(dangerousMelds) == 1:
txt.append(m18n(
- 'claiming %1 is dangerous because you will have to discard a dangerous tile',
- lastDiscard.name()))
+ 'claiming %1 is dangerous because you will have to discard a dangerous tile',
+ lastDiscard.name()))
else:
for meld in dangerousMelds:
txt.append(m18n(
- 'claiming %1 for %2 is dangerous because you will have to discard a dangerous tile',
- lastDiscard.name(), str(meld)))
+ 'claiming %1 for %2 is dangerous because you will have to discard a dangerous tile',
+ lastDiscard.name(), str(meld)))
if not txt:
txt = [m18n('You may say %1', self.i18nName)]
return '<br><br>'.join(txt), warn, ''
@@ -368,7 +368,7 @@ class MessageOriginalCall(NotifyAtOnceMessage, ServerMessage):
return (m18n(
'Discard a tile, declaring Original Call meaning you need only one '
'tile to complete the hand and will not alter the hand in any way (except bonus tiles)'),
- False, '')
+ False, '')
def clientAction(self, client, move):
"""mirror the original call"""
@@ -414,8 +414,8 @@ class MessageDiscard(ClientMessage, ServerMessage):
warn = True
if not txt:
txt = [m18n('discard the least useful tile')]
- txt = '<br><br>'.join(txt)
- return txt, warn, txt
+ txtStr = '<br><br>'.join(txt)
+ return txtStr, warn, txtStr
def clientAction(self, client, move):
"""execute the discard locally"""
@@ -473,8 +473,9 @@ class MessageReadyForGameStart(ServerMessage):
# move.source are the players in seating order
# we cannot just use table.playerNames - the seating order is now
# different (random)
- return client.readyForGameStart(move.tableid, move.gameid,
- move.wantedGame, move.source, shouldSave=move.shouldSave).addCallback(hideTableList)
+ return client.readyForGameStart(
+ move.tableid, move.gameid,
+ move.wantedGame, move.source, shouldSave=move.shouldSave).addCallback(hideTableList)
class MessageNoGameStart(NotifyAtOnceMessage):
@@ -876,6 +877,8 @@ class ChatMessage(object):
def __unicode__(self):
local = self.localtimestamp()
+ # pylint: disable=no-member
+ # pylint says something about NotImplemented, check with later versions
return u'statusMessage=%s %02d:%02d:%02d %s: %s' % (
unicode(self.isStatusMessage),
local.hour,
diff --git a/src/move.py b/src/move.py
index 14fecd7..2d7e663 100644
--- a/src/move.py
+++ b/src/move.py
@@ -76,8 +76,8 @@ class Move(StrMixin):
if isinstance(value, (list, tuple)) and isinstance(value[0], (list, tuple)):
oldValue = value
tuples = []
- for t in oldValue:
- tuples.append(u''.join(unicodeString(x) for x in t))
+ for oldTuple in oldValue:
+ tuples.append(u''.join(unicodeString(x) for x in oldTuple))
value = u','.join(tuples)
if Debug.neutral and key == 'gameid':
result += u' gameid:GAMEID'
diff --git a/src/permutations.py b/src/permutations.py
index 1f57fb4..47251a5 100644
--- a/src/permutations.py
+++ b/src/permutations.py
@@ -67,7 +67,7 @@ class Permutations(object):
if len(groupVariants):
variants.append(groupVariants)
result = []
- for variant in (sum(x, []) for x in itertools.product(*variants)): # pylint: disable=star-args
+ for variant in (sum(x, []) for x in itertools.product(*variants)):
if variant not in result:
result.append(variant)
result = sorted(MeldList(honors + x + boni) for x in result)
@@ -105,9 +105,9 @@ class Permutations(object):
result.append(appendValue)
else:
result = list([list([tuple([x]) for x in values])])
- result = tuple(sorted(set(tuple(tuple(sorted(x)) for x in result))))
- cls.permuteCache[valuesTuple] = result
- return result
+ tupleResult = tuple(sorted(set(tuple(tuple(sorted(x)) for x in result))))
+ cls.permuteCache[valuesTuple] = tupleResult
+ return tupleResult
colorPermCache = {}
@@ -159,7 +159,7 @@ class Permutations(object):
allValues = list(x for x in allValues if x > border)
combinations = list(cls.usefulPermutations(x) for x in groups)
result = []
- for variant in list(itertools.product(*combinations)): # pylint: disable=star-args
+ for variant in list(itertools.product(*combinations)):
melds = []
for block in variant:
for meld in block:
diff --git a/src/player.py b/src/player.py
index a20f947..988fb76 100644
--- a/src/player.py
+++ b/src/player.py
@@ -227,7 +227,7 @@ class Player(StrMixin):
"""a readonly tuple"""
# TODO: str or what?
if not self._hand:
- self._hand = self._computeHand()
+ self._hand = self.computeHand()
return self._hand
@property
@@ -388,7 +388,7 @@ class Player(StrMixin):
self._concealedTiles[0] = tileName
self._hand = None
- def _computeHand(self):
+ def computeHand(self):
"""returns Hand for this player"""
assert not (self._concealedMelds and self._concealedTiles)
melds = ['R' + ''.join(str(x) for x in sorted(self._concealedTiles))]
@@ -406,14 +406,14 @@ class Player(StrMixin):
def _computeHandWithDiscard(self, discard):
"""what if"""
- lastSource = self.lastSource # FIXME: recompute
+ lastSource = self.lastSource # TODO: recompute
save = (self.lastTile, self.lastSource)
try:
self.lastSource = lastSource
if discard:
self.lastTile = discard
self._concealedTiles.append(discard)
- return self._computeHand()
+ return self.computeHand()
finally:
self.lastTile, self.lastSource = save
if discard:
@@ -496,8 +496,9 @@ class PlayingPlayer(Player):
self.game.debug(' with hand being {}'.format(self.hand))
melds = concealed[:]
self.game.winner = self
- assert lastMeld in melds, 'lastMeld not in melds: concealed=%s: melds=%s lastMeld=%s lastTile=%s withDiscard=%s' % (
- self._concealedTiles, melds, lastMeld, lastTile, withDiscard)
+ assert lastMeld in melds, \
+ 'lastMeld not in melds: concealed=%s: melds=%s lastMeld=%s lastTile=%s withDiscard=%s' % (
+ self._concealedTiles, melds, lastMeld, lastTile, withDiscard)
if withDiscard:
PlayingPlayer.addConcealedTiles(
self,
@@ -654,7 +655,7 @@ class PlayingPlayer(Player):
tileName, Tile.unknown)
assert src != dst, (
self, src, dst, tiles, self._concealedTiles)
- if not src in self._concealedTiles:
+ if src not in self._concealedTiles:
logException('%s: showConcealedTiles(%s): %s not in %s.' %
(self, tiles, src, self._concealedTiles))
idx = self._concealedTiles.index(src)
@@ -672,7 +673,7 @@ class PlayingPlayer(Player):
if tile == ignoreDiscard:
ignoreDiscard = None
else:
- if not tile in self._concealedTiles:
+ if tile not in self._concealedTiles:
msg = m18nE(
'%1 claiming MahJongg: She does not really have tile %2')
return msg, self.name, tile
@@ -703,6 +704,7 @@ class PlayingPlayer(Player):
self._hand = None
def robsTile(self):
+ """True if the player is robbing a tile"""
self.lastSource = 'k'
def scoreMatchesServer(self, score):
diff --git a/src/playerlist.py b/src/playerlist.py
index 115ea1e..4164eaa 100644
--- a/src/playerlist.py
+++ b/src/playerlist.py
@@ -145,7 +145,7 @@ class PlayerList(QDialog):
playerId = self._data[name]
query = Query(
"select 1 from game where p0=? or p1=? or p2=? or p3=?",
- (playerId, ) * 4)
+ (playerId, ) * 4)
if len(query.records):
Sorry(
m18n('This player cannot be deleted. There are games associated with %1.', name))
diff --git a/src/predefined.py b/src/predefined.py
index 1421a4d..0392db7 100644
--- a/src/predefined.py
+++ b/src/predefined.py
@@ -45,51 +45,55 @@ class ClassicalChinese(PredefinedRuleset):
def addManualRules(self):
"""those are actually winner rules but in the kajongg scoring mode they must be selected manually"""
# applicable only if we have a concealed meld and a declared kong:
- self.winnerRules.createRule('Last Tile Taken from Dead Wall',
- 'FLastTileFromDeadWall||Olastsource=e', doubles=1,
- description=m18n('The dead wall is also called kong box: The last 16 tiles of the wall '
- 'used as source of replacement tiles'))
- self.winnerRules.createRule('Last Tile is Last Tile of Wall',
- 'FIsLastTileFromWall||Olastsource=z', doubles=1,
- description=m18n('Winner said Mah Jong with the last tile taken from the living end of the wall'))
- self.winnerRules.createRule('Last Tile is Last Tile of Wall Discarded',
- 'FIsLastTileFromWallDiscarded||Olastsource=Z', doubles=1,
- description=m18n('Winner said Mah Jong by claiming the last tile taken from the living end of the '
- 'wall, discarded by another player'))
+ self.winnerRules.createRule(
+ 'Last Tile Taken from Dead Wall',
+ 'FLastTileFromDeadWall||Olastsource=e', doubles=1,
+ description=m18n('The dead wall is also called kong box: The last 16 tiles of the wall '
+ 'used as source of replacement tiles'))
+ self.winnerRules.createRule(
+ 'Last Tile is Last Tile of Wall',
+ 'FIsLastTileFromWall||Olastsource=z', doubles=1,
+ description=m18n('Winner said Mah Jong with the last tile taken from the living end of the wall'))
+ self.winnerRules.createRule(
+ 'Last Tile is Last Tile of Wall Discarded',
+ 'FIsLastTileFromWallDiscarded||Olastsource=Z', doubles=1,
+ description=m18n('Winner said Mah Jong by claiming the last tile taken from the living end of the '
+ 'wall, discarded by another player'))
self.winnerRules.createRule(
'Robbing the Kong', r'FRobbingKong||Olastsource=k', doubles=1,
- description=m18n('Winner said Mah Jong by claiming the 4th tile of a kong another player '
- 'just declared'), debug=True)
- self.winnerRules.createRule('Mah Jongg with Original Call',
- 'FMahJonggWithOriginalCall||Oannouncements=a', doubles=1,
- description=m18n(
- 'Just before the first discard, a player can declare Original Call meaning she needs only one '
- 'tile to complete the hand and announces she will not alter the hand in any way (except bonus tiles)'))
+ description=m18n('Winner said Mah Jong by claiming the 4th tile of a kong another player '
+ 'just declared'), debug=True)
+ self.winnerRules.createRule(
+ 'Mah Jongg with Original Call',
+ 'FMahJonggWithOriginalCall||Oannouncements=a', doubles=1,
+ description=m18n(
+ 'Just before the first discard, a player can declare Original Call meaning she needs only one '
+ 'tile to complete the hand and announces she will not alter the hand in any way (except bonus tiles)'))
self.winnerRules.createRule(
'Dangerous Game', 'FDangerousGame||Opayforall',
- description=m18n('In some situations discarding a tile that has a high chance to help somebody to win '
- 'is declared to be dangerous, and if that tile actually makes somebody win, the discarder '
- 'pays the winner for all'))
+ description=m18n('In some situations discarding a tile that has a high chance to help somebody to win '
+ 'is declared to be dangerous, and if that tile actually makes somebody win, the discarder '
+ 'pays the winner for all'))
self.winnerRules.createRule(
'Twofold Fortune', 'FTwofoldFortune||Oannouncements=t',
- limits=1, description=m18n('Kong after Kong: Declare Kong and a second Kong with the replacement '
- 'tile and Mah Jong with the second replacement tile'))
+ limits=1, description=m18n('Kong after Kong: Declare Kong and a second Kong with the replacement '
+ 'tile and Mah Jong with the second replacement tile'))
# limit hands:
self.winnerRules.createRule(
'Blessing of Heaven', 'FBlessingOfHeaven||Olastsource=1', limits=1,
- description=m18n('East says Mah Jong with the unmodified dealt tiles'))
+ description=m18n('East says Mah Jong with the unmodified dealt tiles'))
self.winnerRules.createRule(
'Blessing of Earth', 'FBlessingOfEarth||Olastsource=1', limits=1,
- description=m18n('South, West or North says Mah Jong with the first tile discarded by East'))
+ description=m18n('South, West or North says Mah Jong with the first tile discarded by East'))
self.winnerRules.createRule(
'East won nine times in a row', 'FEastWonNineTimesInARow||Orotate', limits=1,
- description=m18n('If that happens, East gets a limit score and the winds rotate'))
+ description=m18n('If that happens, East gets a limit score and the winds rotate'))
def addPenaltyRules(self):
"""as the name says"""
self.penaltyRules.createRule(
'False Naming of Discard, Claimed for Mah Jongg and False Declaration of Mah Jongg',
- 'Oabsolute payers=2 payees=2', points=-300)
+ 'Oabsolute payers=2 payees=2', points=-300)
def addHandRules(self):
"""as the name says"""
@@ -105,7 +109,7 @@ class ClassicalChinese(PredefinedRuleset):
doubles=1)
self.handRules.createRule(
'Long Hand', r'FLongHand||Oabsolute', points=0,
- description=m18n('The hand contains too many tiles'))
+ description=m18n('The hand contains too many tiles'))
def addParameterRules(self):
"""as the name says"""
@@ -115,34 +119,35 @@ class ClassicalChinese(PredefinedRuleset):
parameter=0)
self.parameterRules.createRule(
'Minimum number of doubles needed for Mah Jongg',
- 'intminMJDoubles', parameter=0)
+ 'intminMJDoubles', parameter=0)
self.parameterRules.createRule(
'Points for a Limit Hand',
'intlimit||Omin=1',
parameter=500)
self.parameterRules.createRule(
'Play with the roof off', 'boolroofOff', parameter=False,
- description=m18n('Play with no upper scoring limit'))
+ description=m18n('Play with no upper scoring limit'))
self.parameterRules.createRule(
'Claim Timeout',
'intclaimTimeout',
parameter=10)
self.parameterRules.createRule(
'Size of Kong Box', 'intkongBoxSize', parameter=16,
- description=m18n('The Kong Box is used for replacement tiles when declaring kongs'))
+ description=m18n('The Kong Box is used for replacement tiles when declaring kongs'))
self.parameterRules.createRule(
'Play with Bonus Tiles', 'boolwithBonusTiles', parameter=True,
- description=m18n('Bonus tiles increase the luck factor'))
+ description=m18n('Bonus tiles increase the luck factor'))
self.parameterRules.createRule(
'Minimum number of rounds in game',
'intminRounds',
parameter=4)
self.parameterRules.createRule(
'number of allowed chows', 'intmaxChows', parameter=4,
- description=m18n('The number of chows a player may build'))
- self.parameterRules.createRule('must declare calling hand',
- 'boolmustDeclareCallingHand', parameter=False,
- description=m18n('Mah Jongg is only allowed after having declared to have a calling hand'))
+ description=m18n('The number of chows a player may build'))
+ self.parameterRules.createRule(
+ 'must declare calling hand',
+ 'boolmustDeclareCallingHand', parameter=False,
+ description=m18n('Mah Jongg is only allowed after having declared to have a calling hand'))
self.parameterRules.createRule(
'Standard Rotation',
'FStandardRotation||Orotate||Ointernal')
@@ -159,7 +164,7 @@ class ClassicalChinese(PredefinedRuleset):
points=2)
self.winnerRules.createRule(
'Last Tile Completes Pair of Terminals or Honors',
- 'FLastTileCompletesPairMajor', points=4)
+ 'FLastTileCompletesPairMajor', points=4)
self.winnerRules.createRule(
'Last Tile is Only Possible Tile',
'FLastOnlyPossible',
@@ -171,7 +176,7 @@ class ClassicalChinese(PredefinedRuleset):
self.winnerRules.createRule(
'Zero Point Hand', 'FZeroPointHand', doubles=1,
- description=m18n('The hand has 0 basis points excluding bonus tiles'))
+ description=m18n('The hand has 0 basis points excluding bonus tiles'))
self.winnerRules.createRule('No Chow', 'FNoChow', doubles=1)
self.winnerRules.createRule(
'Only Concealed Melds',
@@ -179,31 +184,31 @@ class ClassicalChinese(PredefinedRuleset):
doubles=1)
self.winnerRules.createRule(
'False Color Game', 'FFalseColorGame', doubles=1,
- description=m18n('Only same-colored tiles (only bamboo/stone/character) '
- 'plus any number of winds and dragons'))
+ description=m18n('Only same-colored tiles (only bamboo/stone/character) '
+ 'plus any number of winds and dragons'))
self.winnerRules.createRule(
'True Color Game', 'FTrueColorGame', doubles=3,
- description=m18n('Only same-colored tiles (only bamboo/stone/character)'))
+ description=m18n('Only same-colored tiles (only bamboo/stone/character)'))
self.winnerRules.createRule(
'Concealed True Color Game', 'FConcealedTrueColorGame',
- limits=1, description=m18n('All tiles concealed and of the same suit, no honors'))
+ limits=1, description=m18n('All tiles concealed and of the same suit, no honors'))
self.winnerRules.createRule(
'Only Terminals and Honors', 'FOnlyMajors', doubles=1,
- description=m18n('Only winds, dragons, 1 and 9'))
+ description=m18n('Only winds, dragons, 1 and 9'))
self.winnerRules.createRule('Only Honors', 'FOnlyHonors', limits=1,
description=m18n('Only winds and dragons'))
self.winnerRules.createRule(
'Hidden Treasure', 'FHiddenTreasure', limits=1,
- description=m18n('Only hidden Pungs or Kongs, last tile from wall'))
+ description=m18n('Only hidden Pungs or Kongs, last tile from wall'))
self.winnerRules.createRule(
'Heads and Tails', 'FAllTerminals', limits=1,
- description=m18n('Only 1 and 9'))
+ description=m18n('Only 1 and 9'))
self.winnerRules.createRule(
'Fourfold Plenty', 'FFourfoldPlenty', limits=1,
- description=m18n('4 Kongs'))
+ description=m18n('4 Kongs'))
self.winnerRules.createRule(
'Three Great Scholars', 'FThreeGreatScholars', limits=1,
- description=m18n('3 Pungs or Kongs of dragons'))
+ description=m18n('3 Pungs or Kongs of dragons'))
self.winnerRules.createRule('Four Blessings Hovering over the Door',
'FFourBlessingsHoveringOverTheDoor', limits=1,
description=m18n('4 Pungs or Kongs of winds'))
@@ -214,10 +219,10 @@ class ClassicalChinese(PredefinedRuleset):
description=m18n('Mah Jong with stone 5 from the dead wall'))
self.winnerRules.createRule(
'Plucking the Moon from the Bottom of the Sea', 'FPluckingMoon', limits=1,
- description=m18n('Mah Jong with the last tile from the wall being a stone 1'))
+ description=m18n('Mah Jong with the last tile from the wall being a stone 1'))
self.winnerRules.createRule(
'Scratching a Carrying Pole', 'FScratchingPole', limits=1,
- description=m18n('Robbing the Kong of bamboo 2'))
+ description=m18n('Robbing the Kong of bamboo 2'))
# only hands matching an mjRule can win. Keep this list as short as
# possible. If a special hand matches the standard pattern, do not put it here
@@ -229,7 +234,8 @@ class ClassicalChinese(PredefinedRuleset):
# option internal makes it not show up in the ruleset editor
self.mjRules.createRule(
'Nine Gates', 'FGatesOfHeaven||OlastExtra', limits=1,
- description=m18n('A concealed hand in one color 1112345678999 plus last tile of this suit (from wall or discarded)'))
+ description=m18n(
+ 'A concealed hand in one color 1112345678999 plus last tile of this suit (from wall or discarded)'))
self.mjRules.createRule(
'Thirteen Orphans', 'FThirteenOrphans||Omayrobhiddenkong', limits=1,
description=m18n('13 single tiles: All dragons, winds, 1, 9 and a 14th tile building a pair '
@@ -315,18 +321,19 @@ class ClassicalChineseDMJL(ClassicalChinese):
ClassicalChinese.loadRules(self)
# the squirming snake is only covered by standard mahjongg rule if
# tiles are ordered
- self.mjRules.createRule('Squirming Snake', 'FSquirmingSnake', limits=1,
- description=m18n('All tiles of same color. Pung or Kong of 1 and 9, pair of 2, 5 or 8 and two '
- 'Chows of the remaining values'))
+ self.mjRules.createRule(
+ 'Squirming Snake', 'FSquirmingSnake', limits=1,
+ description=m18n('All tiles of same color. Pung or Kong of 1 and 9, pair of 2, 5 or 8 and two '
+ 'Chows of the remaining values'))
self.handRules.createRule(
'Little Three Dragons', 'FLittleThreeDragons', doubles=1,
- description=m18n('2 Pungs or Kongs of dragons and 1 pair of dragons'))
+ description=m18n('2 Pungs or Kongs of dragons and 1 pair of dragons'))
self.handRules.createRule(
'Big Three Dragons', 'FBigThreeDragons', doubles=2,
- description=m18n('3 Pungs or Kongs of dragons'))
+ description=m18n('3 Pungs or Kongs of dragons'))
self.handRules.createRule(
'Little Four Joys', 'FLittleFourJoys', doubles=1,
- description=m18n('3 Pungs or Kongs of winds and 1 pair of winds'))
+ description=m18n('3 Pungs or Kongs of winds and 1 pair of winds'))
self.handRules.createRule('Big Four Joys', 'FBigFourJoys', doubles=2,
description=m18n('4 Pungs or Kongs of winds'))
@@ -340,16 +347,16 @@ class ClassicalChineseDMJL(ClassicalChinese):
points=-100)
self.penaltyRules.createRule(
'False Declaration of Mah Jongg by One Player',
- 'Oabsolute payees=3', points=-300)
+ 'Oabsolute payees=3', points=-300)
self.penaltyRules.createRule(
'False Declaration of Mah Jongg by Two Players',
- 'Oabsolute payers=2 payees=2', points=-300)
+ 'Oabsolute payers=2 payees=2', points=-300)
self.penaltyRules.createRule(
'False Declaration of Mah Jongg by Three Players',
- 'Oabsolute payers=3', points=-300)
+ 'Oabsolute payers=3', points=-300)
self.penaltyRules.createRule(
'False Naming of Discard, Claimed for Mah Jongg',
- 'Oabsolute payees=3', points=-300)
+ 'Oabsolute payees=3', points=-300)
class ClassicalChineseBMJA(ClassicalChinese):
@@ -385,18 +392,19 @@ class ClassicalChineseBMJA(ClassicalChinese):
del self.mjRules['NineGates']
self.mjRules.createRule(
'Gates of Heaven', 'FGatesOfHeaven||Opair28', limits=1,
- description=m18n('All tiles concealed of same color: Values 1112345678999'
- ' with one pair from 2 to 8 (last tile from wall or discarded)'))
- self.mjRules.createRule('Wriggling Snake', 'FWrigglingSnake', limits=1,
- description=m18n('Pair of 1s and a run from 2 to 9 in the same suit with each of the winds'))
+ description=m18n('All tiles concealed of same color: Values 1112345678999'
+ ' with one pair from 2 to 8 (last tile from wall or discarded)'))
+ self.mjRules.createRule(
+ 'Wriggling Snake', 'FWrigglingSnake', limits=1,
+ description=m18n('Pair of 1s and a run from 2 to 9 in the same suit with each of the winds'))
self.mjRules.createRule(
'Triple Knitting', 'FTripleKnitting', limits=0.5,
- description=m18n('Four sets of three tiles in the different suits and a pair: No Winds or Dragons'))
+ description=m18n('Four sets of three tiles in the different suits and a pair: No Winds or Dragons'))
self.mjRules.createRule('Knitting', 'FKnitting', limits=0.5,
description=m18n('7 pairs of tiles in any 2 out of 3 suits; no Winds or Dragons'))
self.mjRules.createRule(
'All pair honors', 'FAllPairHonors', limits=0.5,
- description=m18n('7 pairs of 1s/9s/Winds/Dragons'))
+ description=m18n('7 pairs of 1s/9s/Winds/Dragons'))
del self.handRules['OwnFlowerandOwnSeason']
del self.handRules['ThreeConcealedPongs']
self.meldRules.createRule('Own Flower', 'FOwnFlower', doubles=1)
@@ -413,12 +421,12 @@ class ClassicalChineseBMJA(ClassicalChinese):
del self.winnerRules['ThreeGreatScholars']
self.winnerRules.createRule(
'Buried Treasure', 'FBuriedTreasure', limits=1,
- description=m18n('Concealed pungs of one suit with winds/dragons and a pair'))
+ description=m18n('Concealed pungs of one suit with winds/dragons and a pair'))
self.winnerRules.createRule('Purity', 'FPurity', doubles=3,
description=m18n('Only same-colored tiles (no chows, dragons or winds)'))
self.winnerRules.createRule(
'Three Great Scholars', 'FThreeGreatScholars||Onochow', limits=1,
- description=m18n('3 Pungs or Kongs of dragons plus any pung/kong and a pair'))
+ description=m18n('3 Pungs or Kongs of dragons plus any pung/kong and a pair'))
orphans = self.mjRules.pop('ThirteenOrphans')
self.mjRules.createRule(
'The 13 Unique Wonders',
@@ -432,7 +440,7 @@ class ClassicalChineseBMJA(ClassicalChinese):
points=-50)
self.penaltyRules.createRule(
'False Declaration of Mah Jongg by One Player',
- 'Oabsolute payees=3', limits=-0.5)
+ 'Oabsolute payees=3', limits=-0.5)
self.winnerRules.createRule(
'False Naming of Discard, Claimed for Mah Jongg',
'FFalseDiscardForMJ||Opayforall')
@@ -451,7 +459,7 @@ class ClassicalChineseBMJA(ClassicalChinese):
limits=0.2)
self.loserRules.createRule(
'Calling for Gates of Heaven', 'FCallingHand||Ohand=GatesofHeaven||Opair28',
- limits=0.4)
+ limits=0.4)
self.loserRules.createRule(
'Calling for Knitting',
'FCallingHand||Ohand=Knitting',
@@ -462,10 +470,10 @@ class ClassicalChineseBMJA(ClassicalChinese):
limits=0.4)
self.loserRules.createRule(
'Calling for The 13 Unique Wonders', 'FCallingHand||Ohand=The13UniqueWonders',
- limits=0.4)
+ limits=0.4)
self.loserRules.createRule(
'Calling for Three Great Scholars', 'FCallingHand||Ohand=ThreeGreatScholars',
- limits=0.4)
+ limits=0.4)
self.loserRules.createRule(
'Calling for All pair honors',
'FCallingHand||Ohand=Allpairhonors',
@@ -476,7 +484,7 @@ class ClassicalChineseBMJA(ClassicalChinese):
limits=0.4)
self.loserRules.createRule(
'Calling for Four Blessings Hovering over the Door',
- 'FCallingHand||Ohand=FourBlessingsHoveringovertheDoor', limits=0.4)
+ 'FCallingHand||Ohand=FourBlessingsHoveringovertheDoor', limits=0.4)
self.loserRules.createRule(
'Calling for Buried Treasure',
'FCallingHand||Ohand=BuriedTreasure',
diff --git a/src/pylintrc b/src/pylintrc
index a5d9c52..e5e4931 100644
--- a/src/pylintrc
+++ b/src/pylintrc
@@ -15,9 +15,6 @@
# pygtk.require().
#init-hook=
-# Profiled execution.
-profile=no
-
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=CVS
@@ -59,7 +56,7 @@ load-plugins=
# W0122 exec statement
# E0211 method has no argument
# disable=R0903,R0902,W0122
-disable=R0903,E0211,I0011,I0013,too-many-ancestors
+disable=R0903,E0211,I0011,I0013,too-many-ancestors,no-member
[REPORTS]
@@ -86,10 +83,6 @@ reports=no
# (R0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
-# Add a comment according to your evaluation note. This is used by the global
-# evaluation report (R0004).
-comment=no
-
# Enable the report(s) with the given id(s).
#enable-report=
@@ -109,10 +102,6 @@ ignore-mixin-members=yes
# (useful for classes with attributes dynamicaly set).
ignored-classes=SQLObject,Message,Ruleset,Move,twisted.internet.reactor
-# When zope mode is activated, consider the acquired-members option to ignore
-# access to some undefined attributes.
-zope=no
-
# List of members which are usually get through zope's acquisition mecanism and
# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
acquired-members=REQUEST,acl_users,aq_parent
@@ -130,9 +119,6 @@ acquired-members=REQUEST,acl_users,aq_parent
#
[BASIC]
-# Required attributes for module, separated by a comma
-required-attributes=
-
# Regular expression which should only match functions or classes name which do
# not require a docstring
no-docstring-rgx=(fget|fset|(__.*__))
@@ -187,7 +173,7 @@ bad-functions=map,filter,apply,input
init-import=no
# A regular expression matching names used for dummy variables (i.e. not used).
-dummy-variables-rgx=_|dummy|fget|fset|loop|(dummy[A-Z][a-zA-Z0-9]*)
+dummy-variables-rgx=_|dummy|fget|fset|(dummy[A-Z][a-zA-Z0-9]*)
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
@@ -262,10 +248,6 @@ max-public-methods=40
#
[CLASSES]
-# List of interface methods to ignore, separated by a comma. This is used for
-# instance to not check methods defines in Zope's Interface base class.
-ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
-
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp,setupUi,setupActions,setupUILastTileMeld,clearHand,__build
diff --git a/src/qt.py b/src/qt.py
index e1ad854..9a180c1 100644
--- a/src/qt.py
+++ b/src/qt.py
@@ -19,11 +19,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
# pylint: disable=unused-import, unused-wildcard-import, wildcard-import
-# pylint: disable=invalid-name
+# pylint: disable=invalid-name, wrong-import-position
-import sip
import sys
+import sip
+
from common import isPython3, Internal
usingQt4 = True # Default for now
@@ -35,9 +36,10 @@ if '--qt5' in sys.argv:
usingQt5 = True
usingQt4 = False
except ImportError as exc:
- Internal.logger.debug('{who}: Cannot import Qt5:{msg}, using Qt4 instead'.format(
- who='Server' if Internal.isServer else 'Client',
- msg=exc.message))
+ Internal.logger.debug(
+ '%s: Cannot import Qt5:%s, using Qt4 instead',
+ 'Server' if Internal.isServer else 'Client',
+ exc.message)
from qt4 import *
else:
from qt4 import *
@@ -62,7 +64,9 @@ class RealQVariant(object):
if isPython3:
def toQVariant(obj=None):
+ """PY3 does not need QVariant anymore"""
return obj
else:
def toQVariant(obj=None):
+ """PY3 does not need QVariant anymore"""
return QVariant(obj)
diff --git a/src/qt4.py b/src/qt4.py
index a5aa5f7..d9dde36 100644
--- a/src/qt4.py
+++ b/src/qt4.py
@@ -18,7 +18,7 @@ along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
-# pylint: disable=unused-import
+# pylint: disable=unused-import, no-name-in-module
from PyQt4 import uic
from PyQt4.QtCore import QT_VERSION_STR
@@ -47,10 +47,6 @@ from PyQt4.QtCore import QRectF
from PyQt4.QtCore import QSize
from PyQt4.QtCore import QSizeF
from PyQt4.QtCore import QSocketNotifier
-try:
- from PyQt4.QtCore import QString
-except ImportError:
- from qstring import QString
from PyQt4.QtCore import QTimer
from PyQt4.QtCore import QTranslator
from PyQt4.QtCore import SLOT
@@ -86,7 +82,6 @@ from PyQt4.QtGui import QImageReader
from PyQt4.QtGui import QItemSelectionModel
from PyQt4.QtGui import QLabel
from PyQt4.QtGui import QLineEdit
-KLineEdit = QLineEdit # pylint: disable=invalid-name
from PyQt4.QtGui import QListWidget
from PyQt4.QtGui import QListWidgetItem
from PyQt4.QtGui import QListView
@@ -132,7 +127,12 @@ from PyQt4.QtGui import QWidget
from PyQt4.QtGui import QValidator
from PyQt4.QtSvg import QGraphicsSvgItem
from PyQt4.QtSvg import QSvgRenderer
+try:
+ from PyQt4.QtCore import QString
+except ImportError:
+ from qstring import QString
+KLineEdit = QLineEdit # pylint: disable=invalid-name
def variantValue(variant):
"""convert QVariant to a python variable"""
diff --git a/src/qt5.py b/src/qt5.py
index 9c1f601..b59f4ef 100644
--- a/src/qt5.py
+++ b/src/qt5.py
@@ -18,7 +18,7 @@ along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
-# pylint: disable=unused-import
+# pylint: disable=unused-import, no-name-in-module
from PyQt5 import uic
from PyQt5.QtCore import PYQT_VERSION_STR
@@ -47,7 +47,6 @@ from PyQt5.QtCore import QRectF
from PyQt5.QtCore import QSize
from PyQt5.QtCore import QSizeF
from PyQt5.QtCore import QSocketNotifier
-from qstring import QString
from PyQt5.QtCore import QTimer
from PyQt5.QtCore import QTranslator
from PyQt5.QtCore import pyqtProperty
@@ -82,8 +81,6 @@ from PyQt5.QtGui import QImageReader
from PyQt5.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QLineEdit
-KLineEdit = QLineEdit # pylint: disable=invalid-name
-# TODO: where?
from PyQt5.QtWidgets import QListWidget
from PyQt5.QtWidgets import QListWidgetItem
from PyQt5.QtWidgets import QListView
@@ -129,7 +126,10 @@ from PyQt5.QtWidgets import QWidget
from PyQt5.QtGui import QValidator
from PyQt5.QtSvg import QGraphicsSvgItem
from PyQt5.QtSvg import QSvgRenderer
+from qstring import QString
+KLineEdit = QLineEdit # pylint: disable=invalid-name
+# TODO: where?
def variantValue(variant):
"""convert QVariant to python variable"""
diff --git a/src/query.py b/src/query.py
index 3c94732..03e4703 100644
--- a/src/query.py
+++ b/src/query.py
@@ -470,8 +470,8 @@ class PrepareDB(object):
"""only try to create it if it does not yet exist. Do not use create if not exists because
we want debug output only if we really create the index"""
if not Query(
- "select 1 from sqlite_master where type='index' and name=?", (
- name,),
+ "select 1 from sqlite_master where type='index' and name=?", (
+ name,),
silent=True).records:
Query("create index %s on %s" % (name, cmd))
diff --git a/src/rule.py b/src/rule.py
index 34e563b..1b41816 100644
--- a/src/rule.py
+++ b/src/rule.py
@@ -52,7 +52,7 @@ class Score(object):
self.limits = type(self.limits)(limits)
unitNames = {m18nE(
- 'points'): 0,
+ 'points'): 0,
m18nE('doubles'): 50,
m18nE('limits'): 9999}
@@ -439,7 +439,7 @@ into a situation where you have to pay a penalty"""))
query = Query(
"select id,hash,name,description from ruleset where hash=?",
(nativeString(self.name),
- ))
+ ))
if len(query.records):
(self.rulesetId, self.__hash, self.name,
self.description) = query.records[0]
@@ -490,7 +490,7 @@ into a situation where you have to pay a penalty"""))
"""returns a Query object with loaded ruleset"""
return Query(
"select ruleset, list, position, name, definition, points, doubles, limits, parameter from rule "
- "where ruleset=? order by list,position", (self.rulesetId,))
+ "where ruleset=? order by list,position", (self.rulesetId,))
def toList(self):
"""returns entire ruleset encoded in a string"""
@@ -730,7 +730,7 @@ into a situation where you have to pay a penalty"""))
qData = Query(
"select name from ruleset where id=?",
(lastUsedId,
- )).records
+ )).records
if qData:
lastUsed = qData[0][0]
for idx, ruleset in enumerate(result):
@@ -938,7 +938,7 @@ class Rule(RuleBase):
logException(
m18nc(
'%1 can be a sentence', '%4 have impossible values %2/%3 in rule "%1"',
- self.name, payers, payees, 'payers/payees'))
+ self.name, payers, payees, 'payers/payees'))
def explain(self, meld):
"""use this rule for scoring"""
@@ -948,8 +948,8 @@ class Rule(RuleBase):
value=meld[0].valueName() if meld else '',
meldType=meld.typeName() if meld else '',
meldName=meld.name() if meld else '',
- tileName=meld[0].name() if meld else ''
- ).replace('&', '').replace(' ', ' ').strip(), self.score.contentStr())
+ tileName=meld[0].name() if meld else '').replace(
+ '&', '').replace(' ', ' ').strip(), self.score.contentStr())
def hashStr(self):
"""
@@ -1034,7 +1034,7 @@ class IntRule(ParameterRule):
if self.parameter < self.minimum:
return m18nc(
'wrong value for rule', '%1: %2 is too small, minimal value is %3',
- m18n(self.name), self.parName, self.minimum)
+ m18n(self.name), self.parName, self.minimum)
class BoolRule(ParameterRule):
diff --git a/src/rulecode.py b/src/rulecode.py
index 6735a66..d4ded2b 100644
--- a/src/rulecode.py
+++ b/src/rulecode.py
@@ -1,3 +1,4 @@
+#pylint: disable=too-many-lines
# -*- coding: utf-8 -*-
"""Copyright (C) 2009-2014 Wolfgang Rohdewald <wolfgang@rohdewald.de>
@@ -252,7 +253,7 @@ class Purity(RuleCode):
class ConcealedTrueColorGame(RuleCode):
def appliesToHand(hand):
- if len(hand.suits) != 1 or not hand.suits < set(Tile.colors):
+ if len(hand.suits) != 1 or hand.suits >= set(Tile.colors):
return False
return not any((x.isExposed and not x.isClaimedKong) for x in hand.melds)
@@ -337,8 +338,7 @@ class StandardMahJongg(MJRule):
return {Tile(group, val0 + 1)}
def winningTileCandidates(cls, hand):
- # pylint:
- # disable=too-many-locals,too-many-return-statements,too-many-branches,too-many-statements
+ # pylint: disable=too-many-locals,too-many-return-statements,too-many-branches,too-many-statements
if len(hand.melds) > 7:
# hope 7 is sufficient, 6 was not
return set()
@@ -381,13 +381,15 @@ class StandardMahJongg(MJRule):
values = sorted(x.value for x in result if x.group == group)
changed = True
while (changed and len(values) > 2
- and values.count(values[0]) == 1
- and values.count(values[1]) == 1
- and values.count(values[2]) == 1):
+ and values.count(values[0]) == 1
+ and values.count(values[1]) == 1
+ and values.count(values[2]) == 1):
changed = False
if values[0] + 2 == values[2] and (len(values) == 3 or values[3] > values[0] + 3):
# logDebug(u'removing first 3 from %s' % values)
meld = Tile(group, values[0]).chow
+ # pylint: disable=not-an-iterable
+ # must be a pylint bug. meld is TileList is list
for pair in meld:
result.remove(pair)
melds.append(meld)
@@ -399,12 +401,14 @@ class StandardMahJongg(MJRule):
return cls.fillChow(group, values[:2])
changed = True
while (changed and len(values) > 2
- and values.count(values[-1]) == 1
- and values.count(values[-2]) == 1
- and values.count(values[-3]) == 1):
+ and values.count(values[-1]) == 1
+ and values.count(values[-2]) == 1
+ and values.count(values[-3]) == 1):
changed = False
if values[-1] - 2 == values[-3] and (len(values) == 3 or values[-4] < values[-1] - 3):
meld = Tile(group, values[-3]).chow
+ # pylint: disable=not-an-iterable
+ # must be a pylint bug. meld is TileList is list
for pair in meld:
result.remove(pair)
melds.append(meld)
@@ -481,7 +485,7 @@ class SquirmingSnake(StandardMahJongg):
std = hand.ruleCache.get(cacheKey, None)
if std is False:
return False
- if len(hand.suits) != 1 or not hand.suits < set(Tile.colors):
+ if len(hand.suits) != 1 or hand.suits >= set(Tile.colors):
return False
values = hand.values
if values.count(1) < 3 or values.count(9) < 3:
@@ -550,7 +554,7 @@ class WrigglingSnake(MJRule):
if Tile.wind not in suits:
return False
suits -= {Tile.wind}
- if len(suits) != 1 or not suits < set(Tile.colors):
+ if len(suits) != 1 or suits >= set(Tile.colors):
return False
if hand.values.count(1) != 2:
return False
@@ -805,7 +809,7 @@ class AllPairHonors(MJRule):
if len(set(hand.tiles)) != 7:
return False
tileCounts = list([len([x for x in hand.tiles if x == y])
- for y in hand.tiles])
+ for y in hand.tiles])
return set(tileCounts) == set([2])
def winningTileCandidates(cls, hand):
@@ -893,7 +897,7 @@ class LittleThreeDragons(RuleCode):
def appliesToHand(hand):
lengths = sorted([min(len(x), 3)
- for x in hand.melds if x.isDragonMeld])
+ for x in hand.melds if x.isDragonMeld])
return lengths == [2, 3, 3]
@@ -1009,7 +1013,7 @@ class EastWonNineTimesInARow(RuleCode):
class GatesOfHeaven(StandardMahJongg):
cache = ()
-# FIXME: in BMJA, 111 and 999 must be concealed, we do not check this
+# TODO: in BMJA, 111 and 999 must be concealed, we do not check this
def computeLastMelds(hand):
return [x for x in hand.melds if hand.lastTile in x]
@@ -1025,7 +1029,7 @@ class GatesOfHeaven(StandardMahJongg):
return False
def maybeCallingOrWon(hand):
- if len(hand.suits) != 1 or not hand.suits < set(Tile.colors):
+ if len(hand.suits) != 1 or hand.suits >= set(Tile.colors):
return False
return not hand.declaredMelds
@@ -1059,12 +1063,12 @@ class GatesOfHeaven(StandardMahJongg):
else:
# we have something of all values
if values.count(1) != 3:
- result = (1,)
+ result = set([1])
elif values.count(9) != 3:
- result = (9,)
+ result = set([9])
else:
if 'pair28' in cls.options:
- result = Tile.minors
+ result = Tile.minors # pylint: disable=redefined-variable-type
else:
result = Tile.numbers
return {Tile(list(hand.suits)[0], x) for x in result}
@@ -1180,6 +1184,8 @@ class OwnFlower(RuleCode):
return meld[0].value == hand.ownWind
def mayApplyToMeld(meld):
+ # pylint: disable=unsubscriptable-object
+ # must be a pylint bug. meld is TileList is list
return meld.isBonus and meld[0].group == Tile.flower
@@ -1189,6 +1195,8 @@ class OwnSeason(RuleCode):
return meld[0].value == hand.ownWind
def mayApplyToMeld(meld):
+ # pylint: disable=unsubscriptable-object
+ # must be a pylint bug. meld is TileList is list
return meld.isBonus and meld[0].group == Tile.season
diff --git a/src/rulesetselector.py b/src/rulesetselector.py
index 2653ebb..87e95a8 100644
--- a/src/rulesetselector.py
+++ b/src/rulesetselector.py
@@ -179,7 +179,7 @@ class RuleModel(TreeModel):
def data(self, index, role): # pylint: disable=no-self-use
"""get data fom model"""
- # pylint: disable=too-many-branches
+ # pylint: disable=too-many-branches,redefined-variable-type
# too many branches
result = None
if index.isValid():
@@ -261,7 +261,7 @@ class RuleModel(TreeModel):
for ridx, ruleList in enumerate(ruleLists):
listIndex = self.index(ridx, 0, rulesetIndex)
ruleItems = list([RuleItem(x)
- for x in ruleList if not 'internal' in x.options])
+ for x in ruleList if 'internal' not in x.options])
self.insertRows(0, ruleItems, listIndex)
@@ -409,7 +409,7 @@ class RuleTreeView(QTreeView):
if self.btnRemove and self.btnCopy:
self.ruleModel = EditableRuleModel(rulesets, self.name)
else:
- self.ruleModel = RuleModel(rulesets, self.name)
+ self.ruleModel = RuleModel(rulesets, self.name) # pylint: disable=redefined-variable-type
self.setItemDelegateForColumn(
1,
RightAlignedCheckboxDelegate(
diff --git a/src/scene.py b/src/scene.py
index 8032416..b4ccc43 100644
--- a/src/scene.py
+++ b/src/scene.py
@@ -18,16 +18,16 @@ along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
+from zope.interface import implements # pylint: disable=unused-import
+
from log import m18n, m18nc, logDebug
from common import LIGHTSOURCES, Internal, isAlive, ZValues, Debug, WINDS
-from common import unicode, nativeString
+from common import nativeString
from twisted.internet.defer import succeed
from qt import Qt, QMetaObject, variantValue
from qt import QGraphicsScene, QGraphicsItem, QGraphicsRectItem, QPen, QColor
-from zope.interface import implements # pylint: disable=unused-import
-
from dialogs import QuestionYesNo
from guiutil import decorateWindow
from board import SelectorBoard, DiscardBoard
@@ -71,7 +71,7 @@ class FocusRect(QGraphicsRectItem):
self.refresh()
@afterQueuedAnimations
- def refresh(self, deferredResult=None):
+ def refresh(self, dummyDeferredResult=None):
"""show/hide on correct position after queued animations end"""
board = self.board
if not isAlive(board) or not isAlive(self):
@@ -167,7 +167,8 @@ class GameScene(SceneWithFocusRect):
self.mainWindow.updateGUI()
self.mainWindow.adjustView()
- def showShadowsChanged(self, oldValue, newValue):
+ def showShadowsChanged(self, dummyOldValue, dummyNewValue):
+ """if the wanted shadow direction changed, apply that change now"""
for uiTile in self.graphicsTileItems():
uiTile.setClippingFlags()
self.applySettings()
diff --git a/src/scoring.py b/src/scoring.py
index 1c6c42c..c81cf9d 100644
--- a/src/scoring.py
+++ b/src/scoring.py
@@ -112,7 +112,7 @@ class SelectPlayers(SelectRuleset):
unusedNames = allNames - self.__selectedNames()
with BlockSignals(self.nameWidgets):
used = set([unicode(x.currentText())
- for x in self.nameWidgets if x.manualSelect])
+ for x in self.nameWidgets if x.manualSelect])
for combo in self.nameWidgets:
if not combo.manualSelect:
if unicode(combo.currentText()) in used:
@@ -160,8 +160,7 @@ class ScoringHandBoard(HandBoard):
def meldVariants(self, tile, lowerHalf):
"""Kong might have variants"""
result = []
- meld = self.uiMeldWithTile(
- tile).meld # pylint: disable=maybe-no-member
+ meld = self.uiMeldWithTile(tile).meld # pylint: disable=no-member
result.append(meld.concealed if lowerHalf else meld.exposed)
if len(meld) == 4:
if lowerHalf:
@@ -390,7 +389,7 @@ class ScoringPlayer(VisiblePlayer, Player):
try:
checked = box.isChecked()
box.setChecked(not checked)
- newHand = self._computeHand()
+ newHand = self.computeHand()
finally:
box.setChecked(checked)
return newHand.score > currentScore
@@ -423,7 +422,7 @@ class ScoringPlayer(VisiblePlayer, Player):
return ''
return 'L%s%s' % (self.lastTile, self.lastMeld)
- def _computeHand(self):
+ def computeHand(self):
"""returns a Hand object, using a cache"""
self.lastTile = Internal.scene.computeLastTile()
self.lastMeld = Internal.scene.computeLastMeld()
diff --git a/src/scoringdialog.py b/src/scoringdialog.py
index 729611f..19ab08b 100644
--- a/src/scoringdialog.py
+++ b/src/scoringdialog.py
@@ -18,6 +18,8 @@ along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
+# pylint: disable=ungrouped-imports
+
from qt import Qt, usingQt5, QPointF, toQVariant, variantValue, \
QSize, QModelIndex, QEvent, QTimer
from kde import usingKDE
@@ -131,11 +133,11 @@ class ScorePlayerItem(ScoreTreeItem):
# point1 - point2) / 16
yield (
fstep * ((2 - fstep) * fstep - 1) * point_1
- + (fstep * fstep * (
- 3 * fstep - 5) + 2) * point0
- + fstep *
- ((4 - 3 * fstep) * fstep + 1) * point1
- + (fstep - 1) * fstep * fstep * point2) / 2
+ + (fstep * fstep * (
+ 3 * fstep - 5) + 2) * point0
+ + fstep *
+ ((4 - 3 * fstep) * fstep + 1) * point1
+ + (fstep - 1) * fstep * fstep * point2) / 2
yield points[-2]
@@ -168,8 +170,7 @@ class ScoreItemDelegate(QStyledItemDelegate):
# separately per cell beause the lines spread vertically over two rows: We would
# have to draw the lines into one big pixmap and copy
# from the into the cells
- painter.drawPolyline(
- *chart) # pylint: disable=star-args
+ painter.drawPolyline(*chart)
return
return QStyledItemDelegate.paint(self, painter, option, index)
@@ -274,8 +275,7 @@ class ScoreModel(TreeModel):
data = []
records = Query(
'select player,rotated,notrotated,penalty,won,prevailing,wind,points,payments,balance,manualrules'
- ' from score where game=? order by player,hand', (game.gameid,)).records
- # pylint: disable=star-args
+ ' from score where game=? order by player,hand', (game.gameid,)).records
humans = sorted(
(x for x in game.players if not x.name.startswith(u'Robot')))
robots = sorted(
@@ -1014,14 +1014,13 @@ class ScoringDialog(QWidget):
"""enable/disable them"""
# if an exclusive rule has been activated, deactivate it for
# all other players
- if isinstance(self.sender(), RuleBox):
- ruleBox = self.sender()
- if ruleBox.isChecked() and ruleBox.rule.exclusive():
- for idx, player in enumerate(self.game.players):
- if ruleBox.parentWidget() != self.details[idx]:
- for pBox in player.manualRuleBoxes:
- if pBox.rule.name == ruleBox.rule.name:
- pBox.setChecked(False)
+ ruleBox = self.sender()
+ if isinstance(ruleBox, RuleBox) and ruleBox.isChecked() and ruleBox.rule.exclusive():
+ for idx, player in enumerate(self.game.players):
+ if ruleBox.parentWidget() != self.details[idx]:
+ for pBox in player.manualRuleBoxes:
+ if pBox.rule.name == ruleBox.rule.name:
+ pBox.setChecked(False)
try:
newState = bool(self.game.winner.handBoard.uiTiles)
except AttributeError:
@@ -1074,9 +1073,9 @@ class ScoringDialog(QWidget):
if player.handBoard and player.handBoard.uiTiles:
spValue.setEnabled(False)
nameLabel.setBuddy(wonBox)
- for loop in range(10):
+ for _ in range(10):
prevTotal = player.handTotal
- handContent = player._computeHand()
+ handContent = player.computeHand()
wonBox.setVisible(handContent.won)
if not wonBox.isVisibleTo(self) and wonBox.isChecked():
wonBox.setChecked(False)
@@ -1137,7 +1136,7 @@ class ScoringDialog(QWidget):
shownTiles.add(tile.tile)
self.cbLastTile.addItem(
QIcon(tile.pixmapFromSvg(pmSize, withBorders=False)),
- '', toQVariant(tile.tile))
+ '', toQVariant(tile.tile))
if indexedTile is tile.tile:
restoredIdx = self.cbLastTile.count() - 1
if not restoredIdx and indexedTile:
diff --git a/src/scoringtest.py b/src/scoringtest.py
index 959068a..8cae4b6 100755
--- a/src/scoringtest.py
+++ b/src/scoringtest.py
@@ -21,8 +21,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from __future__ import print_function
-from common import Debug, isPython3, WINDS # pylint: disable=unused-import
import unittest
+
+from common import Debug, isPython3, WINDS # pylint: disable=unused-import
from player import Players
from game import PlayingGame
from hand import Hand, Score
@@ -45,7 +46,7 @@ Players.createIfUnknown = str
# RULESETS=RULESETS[:1]
GAMES = list([PlayingGame(list(tuple([wind, wind]) for wind in WINDS), x)
- for x in RULESETS])
+ for x in RULESETS])
PROGRAM = None
@@ -170,7 +171,7 @@ class Helpers(object):
if score.total() != total:
result.append('%s %s%s: total %s for %s should be %s' % (
id(
- hand.player.game.ruleset), hand.player.game.ruleset.name, roofOff,
+ hand.player.game.ruleset), hand.player.game.ruleset.name, roofOff,
score.total(), score.__str__(), total))
result.append('hand:%s' % hand)
result.extend(hand.explain())
@@ -222,7 +223,7 @@ class FalseColorGame(Base):
self.scoreTest(
'c1c1c1 c7c7c7 c2c3c4 c5c5 c6c6c6 Lc5c5c5', [Win(32, 3), Win(28)])
self.scoreTest('c1c2c3 wewewe drdrdr dbdb DgDgDg Ldbdbdb', [
- Win(44, 4), Win(38, 2)], winds='wn')
+ Win(44, 4), Win(38, 2)], winds='wn')
self.scoreTest(
's1s1s1 wewewe c2c3c4 c5c5 c6c6c6 Lc5c5c5',
[Win(34),
@@ -233,7 +234,7 @@ class FalseColorGame(Base):
self.scoreTest(
'b1B1B1b1 RB2B3B4B5B6B7B8B8B8 DrDr fe ys LDrDrDr', [Win(74, 2), NoWin()])
self.scoreTest('b1B1B1b1 RB2B2B2B5B6B7B8B8B8 DrDr fe ys LDrDrDr', [
- Win(78, 3), Win(72, 1)], winds='we')
+ Win(78, 3), Win(72, 1)], winds='we')
class WrigglingSnake(Base):
@@ -267,7 +268,7 @@ class SquirmingSnake(Base):
[Win(limits=1),
NoWin()])
self.scoreTest('c1c1c1 c4c5c6 c9c9c9 c6c7c8 RC2C2 Lc1c1c1c1', [
- Win(points=28, doubles=3), NoWin()])
+ Win(points=28, doubles=3), NoWin()])
self.scoreTest(
'c1c1c1 c3c4c5 c9c9c9 c6c7c8 RS2S2 Lc1c1c1c1',
[Win(points=28),
@@ -302,14 +303,14 @@ class Purity(Base):
def testMe(self):
self.scoreTest(
'b1b1b1b1 RB2B3B4B5B6B7B8B8B2B2B2 fe fs fn fw LB3B2B3B4',
- [Win(points=60, doubles=4), NoWin()])
+ [Win(points=60, doubles=4), NoWin()])
self.scoreTest(
'b1b1b1 RB3B3B3B6B6B6B8B8B2B2B2 fe fs fn fw LB3', [Win(54, 6), Win(54, 7)])
self.scoreTest(
'b1b1b1 RB3B3B3B6B6B8B8B2B2B2 fe fs fn fw LB3', [NoWin(28, 1), NoWin(28, 6)])
self.scoreTest(
'c1C1C1c1 c3c3c3 c8c8c8 RC4C5C6C7C7 fs fw ys yw Lc8c8c8c8',
- [Win(72, 4), Win(72, 2)], winds='we')
+ [Win(72, 4), Win(72, 2)], winds='we')
class TrueColorGame(Base):
@@ -319,7 +320,7 @@ class TrueColorGame(Base):
def testMe(self):
self.scoreTest(
'b1b1b1b1 RB2B3B4B5B6B7B8B8B2B2B2 fe fs fn fw LB3B2B3B4',
- [Win(points=60, doubles=4), NoWin()])
+ [Win(points=60, doubles=4), NoWin()])
self.callingTest(
'RB1B2B3B4B5B5B6B6B7B7B8B8B8 LB1', ['b1b3b4b6b7b9', ''])
self.scoreTest(
@@ -336,11 +337,11 @@ class OnlyConcealedMelds(Base):
self.scoreTest(
'RB1B1B1B1B2B3B4B5B6B7B8B9DrDr fe ys LDrDrDr', [Win(46, 2), NoWin()])
self.scoreTest('RB1B1B1B2B2B2B4B4B4B7B8B9DrDr fe ys LDrDrDr', [
- Win(54, 3), Win(48, 1)], winds='we')
+ Win(54, 3), Win(48, 1)], winds='we')
self.scoreTest(
'b1B1B1b1 RB2B3B4B5B6B7B8B8B8DrDr fe ys LDrDrDr', [Win(74, 2), NoWin()])
self.scoreTest('b1B1B1b1 RB2B2B2B5B6B7B8B8B8DrDr fe ys LDrDrDr', [
- Win(78, 3), Win(72, 1)], winds='we')
+ Win(78, 3), Win(72, 1)], winds='we')
class LimitHands(Base):
@@ -488,7 +489,7 @@ class AllHonors(Base):
self.scoreTest(
'wewewe drdrdr RDrDrDrDb wwwwwwww LDb', [NoWin(32, 4), NoWin(32, 4)])
self.scoreTest('wewe wswsws RWnWnWn wwwwwwww b1b1 Lwewewe', [
- NoWin(30, 2), NoWin(30, 1)], winds='ne')
+ NoWin(30, 2), NoWin(30, 1)], winds='ne')
class BuriedTreasure(Base):
@@ -631,7 +632,7 @@ class Rest(Base):
self.scoreTest(
'b3B3B3b3 RDbDbDbDrDrDr wewewewe s2s2 Ls2s2s2', [Win(72, 6), Win(68, 5)])
self.scoreTest('s1s2s3 s1s2s3 b3b3b3 b4b4b4 RB5 fn yn LB5', [
- NoWin(12, 1), NoWin(12, 2)], winds='ne')
+ NoWin(12, 1), NoWin(12, 2)], winds='ne')
self.scoreTest(
'b3b3b3b3 RDbDbDb drdrdr weWeWewe s2s2 Ls2s2s2', [Win(76, 5), Win(72, 5)])
self.scoreTest('s2s2s2 s2s3s4 RB1B1B1B1 c9C9C9c9 Ls2s2s3s4', NoWin(42))
diff --git a/src/server.py b/src/server.py
index 871ff11..f123571 100644
--- a/src/server.py
+++ b/src/server.py
@@ -23,15 +23,14 @@ Twisted Network Programming Essentials by Abe Fettig, 2006
O'Reilly Media, Inc., ISBN 0-596-10032-9
"""
+# pylint: disable=wrong-import-order, wrong-import-position
+
import sys
import os
-import random
-import traceback
-if os.name != 'nt':
- import resource
-import datetime
import logging
-from itertools import chain
+from signal import signal, SIGABRT, SIGINT, SIGTERM
+
+from zope.interface import implementer
def cleanExit(*dummyArgs):
@@ -55,7 +54,6 @@ def cleanExit(*dummyArgs):
except ReactorNotRunning:
pass
-from signal import signal, SIGABRT, SIGINT, SIGTERM
signal(SIGABRT, cleanExit)
signal(SIGINT, cleanExit)
signal(SIGTERM, cleanExit)
@@ -65,58 +63,29 @@ if os.name != 'nt':
signal(SIGQUIT, cleanExit)
-from common import Options, Internal, unicode, WINDS, nativeString
+from common import Options, Internal, Debug
Internal.isServer = True
Internal.logPrefix = 'S'
from twisted.spread import pb
from twisted.internet import error
from twisted.internet.defer import maybeDeferred, fail, succeed
-from zope.interface import implementer
from twisted.cred import checkers, portal, credentials, error as credError
from twisted.internet import reactor
from twisted.internet.error import ReactorNotRunning
reactor.addSystemEventTrigger('before', 'shutdown', cleanExit)
Internal.reactor = reactor
-from tile import Tile, TileList, elements
-from game import PlayingGame
from player import Players
-from wall import WallEmpty
-from client import Client, Table
from query import Query, initDb
-from meld import Meld, MeldList
-from log import m18n, m18nE, m18ncE, logDebug, logWarning, logError, SERVERMARK
-from util import Duration, elapsedSince
+from log import m18n, m18nE, logDebug, logWarning, logError, SERVERMARK
+from util import elapsedSince
from message import Message, ChatMessage
-from common import Debug
-from sound import Voice
from deferredutil import DeferredBlock
from rule import Ruleset
-
-
-def srvMessage(*args):
- """
- concatenate all args needed for m18n encoded in one string.
- For an explanation see util.translateServerMessage.
-
- @returns: The string to be wired.
- @rtype: C{str}, utf-8 encoded
- """
- strArgs = []
- for arg in args:
- if isinstance(arg, unicode):
- arg = arg.encode('utf-8')
- else:
- arg = str(arg).encode('utf-8')
- strArgs.append(arg)
- mark = SERVERMARK.encode()
- return mark + mark.join(strArgs) + mark
-
-
-def srvError(cls, *args):
- """raise an exception, passing args as a single string"""
- raise cls(srvMessage(*args))
+from servercommon import srvError, srvMessage
+from user import User
+from servertable import ServerTable, ServerGame
@implementer(checkers.ICredentialsChecker)
@@ -162,854 +131,6 @@ class DBPasswordChecker(object):
return userid
-class ServerGame(PlayingGame):
-
- """the central game instance on the server"""
- # pylint: disable=too-many-arguments, too-many-public-methods
-
- def __init__(self, names, ruleset, gameid=None, wantedGame=None,
- client=None, playOpen=False, autoPlay=False):
- PlayingGame.__init__(
- self,
- names,
- ruleset,
- gameid,
- wantedGame,
- client,
- playOpen,
- autoPlay)
- self.shouldSave = True
-
- def throwDices(self):
- """sets random living and kongBox
- sets divideAt: an index for the wall break"""
- self.wall.tiles.sort()
- self.randomGenerator.shuffle(self.wall.tiles)
- PlayingGame.throwDices(self)
-
- def initHand(self):
- """Happens only on server: every player gets 13 tiles (including east)"""
- self.throwDices()
- self.wall.divide()
- for player in self.players:
- player.clearHand()
- # 13 tiles at least, with names as given by wall
- # compensate boni
- while len(player.concealedTiles) != 13:
- player.addConcealedTiles(self.wall.deal())
- PlayingGame.initHand(self)
-
-
-class ServerTable(Table):
-
- """a table on the game server"""
- # pylint: disable=too-many-arguments
-
- def __init__(self, server, owner, ruleset, suspendedAt,
- playOpen, autoPlay, wantedGame, tableId=None):
- if tableId is None:
- tableId = server.generateTableId()
- Table.__init__(
- self,
- tableId,
- ruleset,
- suspendedAt,
- False,
- playOpen,
- autoPlay,
- wantedGame)
- self.server = server
- self.owner = owner
- self.users = [owner] if owner else []
- self.remotes = {} # maps client connections to users
- self.game = None
- self.client = None
- server.tables[self.tableid] = self
- if Debug.table:
- logDebug(u'new table %s' % self)
-
- def hasName(self, name):
- """returns True if one of the players in the game is named 'name'"""
- return bool(self.game) and any(x.name == name for x in self.game.players)
-
- def asSimpleList(self, withFullRuleset=False):
- """return the table attributes to be sent to the client"""
- game = self.game
- onlineNames = [x.name for x in self.users]
- if self.suspendedAt:
- names = tuple(x.name for x in game.players)
- else:
- names = tuple(x.name for x in self.users)
- online = tuple(bool(x in onlineNames) for x in names)
- if game:
- endValues = game.handctr, dict(
- (x.wind, x.balance) for x in game.players)
- else:
- endValues = None
- if withFullRuleset:
- ruleset = self.ruleset.toList()
- else:
- ruleset = self.ruleset.hash
- return list(
- [self.tableid, ruleset, game.gameid if game else None, self.suspendedAt, self.running,
- self.playOpen, self.autoPlay, self.wantedGame, names, online, endValues])
-
- def maxSeats(self):
- """for a new game: 4. For a suspended game: The
- number of humans before suspending"""
- result = 4
- if self.suspendedAt:
- result -= sum(x.name.startswith(u'Robot ')
- for x in self.game.players)
- return result
-
- def sendChatMessage(self, chatLine):
- """sends a chat messages to all clients"""
- if Debug.chat:
- logDebug(u'server sends chat msg %s' % chatLine)
- if self.suspendedAt:
- chatters = []
- for player in self.game.players:
- chatters.extend(
- x for x in self.server.srvUsers if x.name == player.name)
- else:
- chatters = self.users
- for other in chatters:
- self.server.callRemote(other, 'chat', chatLine.asList())
-
- def addUser(self, user):
- """add user to this table"""
- if user.name in list(x.name for x in self.users):
- raise srvError(pb.Error, m18nE('You already joined this table'))
- if len(self.users) == self.maxSeats():
- raise srvError(pb.Error, m18nE('All seats are already taken'))
- self.users.append(user)
- if Debug.table:
- logDebug(u'%s seated on table %s' % (user.name, self))
- self.sendChatMessage(ChatMessage(self.tableid, user.name,
- m18nE('takes a seat'), isStatusMessage=True))
-
- def delUser(self, user):
- """remove user from this table"""
- if user in self.users:
- self.running = False
- self.users.remove(user)
- self.sendChatMessage(ChatMessage(self.tableid, user.name,
- m18nE('leaves the table'), isStatusMessage=True))
- if user is self.owner:
- # silently pass ownership
- if self.users:
- self.owner = self.users[0]
- if Debug.table:
- logDebug(u'%s leaves table %d, %s is the new owner' % (
- user.name, self.tableid, self.owner))
- else:
- if Debug.table:
- logDebug(u'%s leaves table %d, table is now empty' % (
- user.name, self.tableid))
- else:
- if Debug.table:
- logDebug(u'%s leaves table %d, %s stays owner' % (
- user.name, self.tableid, self.owner))
-
- def __unicode__(self):
- """for debugging output"""
- onlineNames = list(x.name + (u'(Owner)' if x == self.owner.name else u'')
- for x in self.users)
- offlineString = u''
- if self.game:
- offlineNames = list(x.name for x in self.game.players if x.name not in onlineNames
- and not x.name.startswith(u'Robot'))
- if offlineNames:
- offlineString = u' offline:' + u','.join(offlineNames)
- return u'%d(%s%s)' % (self.tableid, u','.join(onlineNames), offlineString)
-
- def calcGameId(self):
- """based upon the max gameids we got from the clients, propose
- a new one, we want to use the same gameid in all data bases"""
- serverMaxGameId = Query('select max(id) from game').records[0][0]
- serverMaxGameId = int(serverMaxGameId) if serverMaxGameId else 0
- gameIds = [x.maxGameId for x in self.users]
- gameIds.append(serverMaxGameId)
- return max(gameIds) + 1
-
- def __prepareNewGame(self):
- """returns a new game object"""
- names = list(x.name for x in self.users)
- # the server and all databases save the english name but we
- # want to make sure a translation exists for the client GUI
- robotNames = [
- m18ncE(
- 'kajongg, name of robot player, to be translated',
- u'Robot 1'),
- m18ncE(
- 'kajongg, name of robot player, to be translated',
- u'Robot 2'),
- m18ncE('kajongg, name of robot player, to be translated', u'Robot 3')]
- while len(names) < 4:
- names.append(robotNames[3 - len(names)])
- names = list(tuple([WINDS[idx], name])
- for idx, name in enumerate(names))
- self.client = Client()
- # Game has a weakref to client, so we must keep
- # it!
- return ServerGame(names, self.ruleset, client=self.client,
- playOpen=self.playOpen, autoPlay=self.autoPlay, wantedGame=self.wantedGame)
-
- def userForPlayer(self, player):
- """finds the table user corresponding to player"""
- for result in self.users:
- if result.name == player.name:
- return result
-
- def __connectPlayers(self):
- """connects client instances with the game players"""
- game = self.game
- for player in game.players:
- remote = self.userForPlayer(player)
- if not remote:
- # we found a robot player, its client runs in this server
- # process
- remote = Client(player.name)
- remote.table = self
- self.remotes[player] = remote
-
- def __checkDbIdents(self):
- """for 4 players, we have up to 4 data bases:
- more than one player might use the same data base.
- However the server always needs to use its own data base.
- If a data base is used by more than one client, only one of
- them should update. Here we set shouldSave for all players,
- while the server always saves"""
- serverIdent = Internal.db.identifier
- dbIdents = set()
- game = self.game
- for player in game.players:
- player.shouldSave = False
- if isinstance(self.remotes[player], User):
- dbIdent = self.remotes[player].dbIdent
- assert dbIdent != serverIdent, \
- 'client and server try to use the same database:%s' % \
- Internal.db.path
- player.shouldSave = dbIdent not in dbIdents
- dbIdents.add(dbIdent)
-
- def readyForGameStart(self, user):
- """the table initiator told us he wants to start the game"""
- if len(self.users) < self.maxSeats() and self.owner != user:
- raise srvError(pb.Error,
- m18nE(
- 'Only the initiator %1 can start this game, you are %2'),
- self.owner.name, user.name)
- if self.suspendedAt:
- self.__connectPlayers()
- self.__checkDbIdents()
- self.initGame()
- else:
- self.game = self.__prepareNewGame()
- self.__connectPlayers()
- self.__checkDbIdents()
- self.proposeGameId(self.calcGameId())
- # TODO: remove table for all other srvUsers out of sight
-
- def proposeGameId(self, gameid):
- """server proposes an id to the clients ands waits for answers"""
- while True:
- query = Query('insert into game(id,seed) values(?,?)',
- (gameid, 'proposed'), mayFail=True, failSilent=True)
- if not query.failure:
- break
- gameid += random.randrange(1, 100)
- block = DeferredBlock(self)
- for player in self.game.players:
- if player.shouldSave and isinstance(self.remotes[player], User):
- # do not ask robot players, they use the server data base
- block.tellPlayer(player, Message.ProposeGameId, gameid=gameid)
- block.callback(self.collectGameIdAnswers, gameid)
-
- def collectGameIdAnswers(self, requests, gameid):
- """clients answered if the proposed game id is free"""
- if requests:
- # when errors happen, there might be no requests left
- for msg in requests:
- if msg.answer == Message.NO:
- self.proposeGameId(gameid + 1)
- return
- elif msg.answer != Message.OK:
- raise srvError(
- pb.Error,
- 'collectGameIdAnswers got neither NO nor OK')
- self.game.gameid = gameid
- self.initGame()
-
- def initGame(self):
- """ask clients if they are ready to start"""
- game = self.game
- game.saveStartTime()
- block = DeferredBlock(self)
- for player in game.players:
- block.tellPlayer(
- player, Message.ReadyForGameStart, tableid=self.tableid,
- gameid=game.gameid, shouldSave=player.shouldSave,
- wantedGame=game.wantedGame, source=list((x.wind, x.name) for x in game.players))
- block.callback(self.startGame)
-
- def startGame(self, requests):
- """if all players said ready, start the game"""
- for user in self.users:
- userRequests = list(x for x in requests if x.user == user)
- if not userRequests or userRequests[0].answer == Message.NoGameStart:
- if Debug.table:
- if not userRequests:
- logDebug(
- u'Server.startGame: found no request for user %s' %
- user.name)
- else:
- logDebug(
- u'Server.startGame: %s said NoGameStart' %
- user.name)
- self.game = None
- return
- if Debug.table:
- logDebug(u'Game starts on table %s' % self)
- elementIter = iter(elements.all(self.game.ruleset))
- wallSize = len(self.game.wall.tiles)
- self.game.wall.tiles = []
- for _ in range(wallSize):
- self.game.wall.tiles.append(next(elementIter).concealed)
- assert isinstance(self.game, ServerGame), self.game
- self.running = True
- self.__adaptOtherTables()
- self.sendVoiceIds()
-
- def __adaptOtherTables(self):
- """if the players on this table also reserved seats on other tables, clear them
- make running table invisible for other users"""
- for user in self.users:
- for tableid in self.server.tablesWith(user):
- if tableid != self.tableid:
- self.server.leaveTable(user, tableid)
- foreigners = list(
- x for x in self.server.srvUsers if x not in self.users)
- if foreigners:
- if Debug.table:
- logDebug(
- u'make running table %s invisible for %s' %
- (self, ','.join(str(x) for x in foreigners)))
- for srvUser in foreigners:
- self.server.callRemote(
- srvUser,
- 'tableRemoved',
- self.tableid,
- '')
-
- def sendVoiceIds(self):
- """tell each player what voice ids the others have. By now the client has a Game instance!"""
- humanPlayers = [
- x for x in self.game.players if isinstance(self.remotes[x], User)]
- if len(humanPlayers) < 2 or not any(self.remotes[x].voiceId for x in humanPlayers):
- # no need to pass around voice data
- self.assignVoices()
- return
- block = DeferredBlock(self)
- for player in humanPlayers:
- remote = self.remotes[player]
- if remote.voiceId:
- # send it to other human players:
- others = [x for x in humanPlayers if x != player]
- if Debug.sound:
- logDebug(u'telling other human players that %s has voiceId %s' % (
- player.name, remote.voiceId))
- block.tell(
- player,
- others,
- Message.VoiceId,
- source=remote.voiceId)
- block.callback(self.collectVoiceData)
-
- def collectVoiceData(self, requests):
- """collect voices of other players"""
- if not self.running:
- return
- block = DeferredBlock(self)
- voiceDataRequests = []
- for request in requests:
- if request.answer == Message.ClientWantsVoiceData:
- # another human player requests sounds for voiceId
- voiceId = request.args[0]
- voiceFor = [x for x in self.game.players if isinstance(self.remotes[x], User)
- and self.remotes[x].voiceId == voiceId][0]
- voiceFor.voice = Voice(voiceId)
- if Debug.sound:
- logDebug(
- u'client %s wants voice data %s for %s' %
- (request.user.name, request.args[0], voiceFor))
- voiceDataRequests.append((request.user, voiceFor))
- if not voiceFor.voice.oggFiles():
- # the server does not have it, ask the client with that
- # voice
- block.tell(
- voiceFor,
- voiceFor,
- Message.ServerWantsVoiceData)
- block.callback(self.sendVoiceData, voiceDataRequests)
-
- def sendVoiceData(self, requests, voiceDataRequests):
- """sends voice sounds to other human players"""
- self.processAnswers(requests)
- block = DeferredBlock(self)
- for voiceDataRequester, voiceFor in voiceDataRequests:
- # this player requested sounds for voiceFor
- voice = voiceFor.voice
- content = voice.archiveContent
- if content:
- if Debug.sound:
- logDebug(
- u'server got voice data %s for %s from client' %
- (voiceFor.voice, voiceFor.name))
- block.tell(
- voiceFor,
- voiceDataRequester,
- Message.VoiceData,
- md5sum=voice.md5sum,
- source=content)
- elif Debug.sound:
- logDebug(u'server got empty voice data %s for %s from client' % (
- voice, voiceFor.name))
- block.callback(self.assignVoices)
-
- def assignVoices(self, dummyResults=None):
- """now all human players have all voice data needed"""
- humanPlayers = [
- x for x in self.game.players if isinstance(self.remotes[x], User)]
- block = DeferredBlock(self)
- block.tell(None, humanPlayers, Message.AssignVoices)
- block.callback(self.startHand)
-
- def pickTile(self, dummyResults=None, deadEnd=False):
- """the active player gets a tile from wall. Tell all clients."""
- if not self.running:
- return
- player = self.game.activePlayer
- try:
- tile = player.pickedTile(deadEnd)
- except WallEmpty:
- self.endHand()
- else:
- self.game.lastDiscard = None
- block = DeferredBlock(self)
- block.tellPlayer(
- player,
- Message.PickedTile,
- tile=tile,
- deadEnd=deadEnd)
- showTile = tile if tile.isBonus or self.game.playOpen else Tile.unknown
- block.tellOthers(
- player,
- Message.PickedTile,
- tile=showTile,
- deadEnd=deadEnd)
- block.callback(self.moved)
-
- def pickKongReplacement(self, requests=None):
- """the active player gets a tile from the dead end. Tell all clients."""
- requests = self.prioritize(requests)
- if requests and requests[0].answer == Message.MahJongg:
- # somebody robs my kong
- requests[0].answer.serverAction(self, requests[0])
- else:
- self.pickTile(requests, deadEnd=True)
-
- def clientDiscarded(self, msg):
- """client told us he discarded a tile. Check for consistency and tell others."""
- if not self.running:
- return
- player = msg.player
- assert player == self.game.activePlayer
- tile = Tile(msg.args[0])
- if tile not in player.concealedTiles:
- self.abort(
- u'player %s discarded %s but does not have it' %
- (player, tile))
- return
- dangerousText = self.game.dangerousFor(player, tile)
- mustPlayDangerous = player.mustPlayDangerous()
- violates = player.violatesOriginalCall(tile)
- self.game.hasDiscarded(player, tile)
- block = DeferredBlock(self)
- block.tellAll(player, Message.Discard, tile=tile)
- block.callback(self._clientDiscarded2, msg, dangerousText, mustPlayDangerous, violates)
-
- def _clientDiscarded2(self, dummyResults, msg, dangerousText, mustPlayDangerous, violates):
- """client told us he discarded a tile. Continue, check for calling"""
- block = DeferredBlock(self)
- player = msg.player
- if violates:
- if Debug.originalCall:
- tile = Tile(msg.args[0])
- logDebug(u'%s just violated OC with %s' % (player, tile))
- player.mayWin = False
- block.tellAll(player, Message.ViolatesOriginalCall)
- block.callback(self._clientDiscarded3, msg, dangerousText, mustPlayDangerous)
-
- def _clientDiscarded3(self, dummyResults, msg, dangerousText, mustPlayDangerous):
- """client told us he discarded a tile. Continue, check for calling"""
- block = DeferredBlock(self)
- player = msg.player
- if self.game.ruleset.mustDeclareCallingHand and not player.isCalling:
- if player.hand.callingHands:
- player.isCalling = True
- block.tellAll(player, Message.Calling)
- block.callback(self._clientDiscarded4, msg, dangerousText, mustPlayDangerous)
-
- def _clientDiscarded4(self, dummyResults, msg, dangerousText, mustPlayDangerous):
- """client told us he discarded a tile. Continue, check for dangerous game"""
- block = DeferredBlock(self)
- player = msg.player
- if dangerousText:
- if mustPlayDangerous and player.lastSource not in 'dZ':
- if Debug.dangerousGame:
- tile = Tile(msg.args[0])
- logDebug(u'%s claims no choice. Discarded %s, keeping %s. %s' %
- (player, tile, ''.join(player.concealedTiles), ' / '.join(dangerousText)))
- player.claimedNoChoice = True
- block.tellAll(
- player,
- Message.NoChoice,
- tiles=TileList(player.concealedTiles))
- else:
- player.playedDangerous = True
- if Debug.dangerousGame:
- tile = Tile(msg.args[0])
- logDebug(u'%s played dangerous. Discarded %s, keeping %s. %s' %
- (player, tile, ''.join(player.concealedTiles), ' / '.join(dangerousText)))
- block.tellAll(
- player,
- Message.DangerousGame,
- tiles=TileList(player.concealedTiles))
- if msg.answer == Message.OriginalCall:
- player.isCalling = True
- block.callback(self.clientMadeOriginalCall, msg)
- else:
- block.callback(self._askForClaims, msg)
-
- def clientMadeOriginalCall(self, dummyResults, msg):
- """first tell everybody about original call
- and then treat the implicit discard"""
- msg.player.originalCall = True
- if Debug.originalCall:
- logDebug(u'server.clientMadeOriginalCall: %s' % msg.player)
- block = DeferredBlock(self)
- block.tellAll(msg.player, Message.OriginalCall)
- block.callback(self._askForClaims, msg)
-
- def startHand(self, dummyResults=None):
- """all players are ready to start a hand, so do it"""
- if self.running:
- self.game.prepareHand()
- self.game.initHand()
- block = self.tellAll(None, Message.InitHand,
- divideAt=self.game.divideAt)
- block.callback(self.divided)
-
- def divided(self, dummyResults=None):
- """the wall is now divided for all clients"""
- if not self.running:
- return
- block = DeferredBlock(self)
- for clientPlayer in self.game.players:
- for player in self.game.players:
- if player == clientPlayer or self.game.playOpen:
- tiles = player.concealedTiles
- else:
- tiles = TileList(Tile.unknown * 13)
- block.tell(player, clientPlayer, Message.SetConcealedTiles,
- tiles=TileList(chain(tiles, player.bonusTiles)))
- block.callback(self.dealt)
-
- def endHand(self, dummyResults=None):
- """hand is over, show all concealed tiles to all players"""
- if not self.running:
- return
- if self.game.playOpen:
- self.saveHand()
- else:
- block = DeferredBlock(self)
- for player in self.game.players:
- # there might be no winner, winner.others() would be wrong
- if player != self.game.winner:
- # the winner tiles are already shown in claimMahJongg
- block.tellOthers(
- player, Message.ShowConcealedTiles, show=True,
- tiles=TileList(player.concealedTiles))
- block.callback(self.saveHand)
-
- def saveHand(self, dummyResults=None):
- """save the hand to the database and proceed to next hand"""
- if not self.running:
- return
- self.tellAll(None, Message.SaveHand, self.nextHand)
- self.game.saveHand()
-
- def nextHand(self, dummyResults):
- """next hand: maybe rotate"""
- if not self.running:
- return
- DeferredBlock.garbageCollection()
- for block in DeferredBlock.blocks:
- if block.table == self:
- logError(
- u'request left from previous hand: %s' %
- block.outstandingUnicode())
- token = self.game.handId.prompt(
- withAI=False) # we need to send the old token until the
- # clients started the new hand
- rotateWinds = self.game.maybeRotateWinds()
- if self.game.finished():
- self.server.removeTable(
- self,
- 'gameOver',
- m18nE('Game <numid>%1</numid> is over!'),
- self.game.seed)
- if Debug.process and os.name != 'nt':
- logDebug(
- u'MEM:%s' %
- resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
- return
- self.game.sortPlayers()
- playerNames = list((x.wind, x.name) for x in self.game.players)
- self.tellAll(None, Message.ReadyForHandStart, self.startHand,
- source=playerNames, rotateWinds=rotateWinds, token=token)
-
- def abort(self, message, *args):
- """abort the table. Reason: message/args"""
- self.server.removeTable(self, 'abort', message, *args)
-
- def claimTile(self, player, claim, meldTiles, nextMessage):
- """a player claims a tile for pung, kong or chow.
- meldTiles contains the claimed tile, concealed"""
- if not self.running:
- return
- lastDiscard = self.game.lastDiscard
- # if we rob a tile, self.game.lastDiscard has already been set to the
- # robbed tile
- hasTiles = Meld(meldTiles[:])
- discardingPlayer = self.game.activePlayer
- hasTiles = hasTiles.without(lastDiscard)
- meld = Meld(meldTiles)
- if len(meld) != 4 and not (meld.isPair or meld.isPungKong or meld.isChow):
- msg = m18nE('%1 wrongly said %2 for meld %3')
- self.abort(msg, player.name, claim.name, str(meld))
- return
- if not player.hasConcealedTiles(hasTiles):
- msg = m18nE(
- '%1 wrongly said %2: claims to have concealed tiles %3 but only has %4')
- self.abort(
- msg,
- player.name,
- claim.name,
- ' '.join(hasTiles),
- ''.join(player.concealedTiles))
- return
- # update our internal state before we listen to the clients again
- self.game.discardedTiles[lastDiscard.exposed] -= 1
- self.game.activePlayer = player
- if lastDiscard:
- player.lastTile = lastDiscard.exposed
- player.lastSource = 'd'
- player.exposeMeld(hasTiles, lastDiscard)
- self.game.lastDiscard = None
- block = DeferredBlock(self)
- if (nextMessage != Message.Kong
- and self.game.dangerousFor(discardingPlayer, lastDiscard)
- and discardingPlayer.playedDangerous):
- player.usedDangerousFrom = discardingPlayer
- if Debug.dangerousGame:
- logDebug(u'%s claims dangerous tile %s discarded by %s' %
- (player, lastDiscard, discardingPlayer))
- block.tellAll(
- player,
- Message.UsedDangerousFrom,
- source=discardingPlayer.name)
- block.tellAll(player, nextMessage, meld=meld)
- if claim == Message.Kong:
- block.callback(self.pickKongReplacement)
- else:
- block.callback(self.moved)
-
- def declareKong(self, player, meldTiles):
- """player declares a Kong, meldTiles is a list"""
- kongMeld = Meld(meldTiles)
- if not player.hasConcealedTiles(kongMeld) and kongMeld[0].exposed.pung not in player.exposedMelds:
- # pylint: disable=star-args
- msg = m18nE('declareKong:%1 wrongly said Kong for meld %2')
- args = (player.name, str(kongMeld))
- logDebug(m18n(msg, *args))
- logDebug(
- u'declareKong:concealedTiles:%s' %
- ''.join(player.concealedTiles))
- logDebug(u'declareKong:concealedMelds:%s' %
- ' '.join(str(x) for x in player.concealedMelds))
- logDebug(u'declareKong:exposedMelds:%s' %
- ' '.join(str(x) for x in player.exposedMelds))
- self.abort(msg, *args)
- return
- player.exposeMeld(kongMeld)
- self.tellAll(
- player,
- Message.DeclaredKong,
- self.pickKongReplacement,
- meld=kongMeld)
-
- def claimMahJongg(self, msg):
- """a player claims mah jongg. Check this and
- if correct, tell all. Otherwise abort game, kajongg client is faulty"""
- if not self.running:
- return
- player = msg.player
- concealedMelds = MeldList(msg.args[0])
- withDiscard = Tile(msg.args[1]) if msg.args[1] else None
- lastMeld = Meld(msg.args[2])
- if self.game.ruleset.mustDeclareCallingHand:
- assert player.isCalling, '%s %s %s says MJ but never claimed: concmelds:%s withdiscard:%s lastmeld:%s' % (
- self.game.handId, player.hand, player, concealedMelds, withDiscard, lastMeld)
- discardingPlayer = self.game.activePlayer
- lastMove = next(self.game.lastMoves(withoutNotifications=True))
- robbedTheKong = lastMove.message == Message.DeclaredKong
- if robbedTheKong:
- player.robsTile()
- withDiscard = lastMove.meld[0].concealed
- lastMove.player.robTileFrom(withDiscard)
- msgArgs = player.showConcealedMelds(concealedMelds, withDiscard)
- if msgArgs:
- self.abort(*msgArgs) # pylint: disable=star-args
- return
- player.declaredMahJongg(
- concealedMelds,
- withDiscard,
- player.lastTile,
- lastMeld)
- if not player.hand.won:
- msg = m18nE('%1 claiming MahJongg: This is not a winning hand: %2')
- self.abort(msg, player.name, player.hand.string)
- return
- block = DeferredBlock(self)
- if robbedTheKong:
- block.tellAll(player, Message.RobbedTheKong, tile=withDiscard)
- if (player.lastSource == 'd'
- and self.game.dangerousFor(discardingPlayer, player.lastTile)
- and discardingPlayer.playedDangerous):
- player.usedDangerousFrom = discardingPlayer
- if Debug.dangerousGame:
- logDebug(u'%s wins with dangerous tile %s from %s' %
- (player, self.game.lastDiscard, discardingPlayer))
- block.tellAll(
- player,
- Message.UsedDangerousFrom,
- source=discardingPlayer.name)
- block.tellAll(
- player, Message.MahJongg, melds=concealedMelds, lastTile=player.lastTile,
- lastMeld=lastMeld, withDiscardTile=withDiscard)
- block.callback(self.endHand)
-
- def dealt(self, dummyResults):
- """all tiles are dealt, ask east to discard a tile"""
- if self.running:
- self.tellAll(
- self.game.activePlayer,
- Message.ActivePlayer,
- self.pickTile)
-
- def nextTurn(self):
- """the next player becomes active"""
- if self.running:
- # the player might just have disconnected
- self.game.nextTurn()
- self.tellAll(
- self.game.activePlayer,
- Message.ActivePlayer,
- self.pickTile)
-
- def prioritize(self, requests):
- """returns only requests we want to execute"""
- if not self.running:
- return
- answers = [
- x for x in requests if x.answer not in [
- Message.NoClaim,
- Message.OK,
- None]]
- if len(answers) > 1:
- claims = [
- Message.MahJongg,
- Message.Kong,
- Message.Pung,
- Message.Chow]
- for claim in claims:
- if claim in [x.answer for x in answers]:
- # ignore claims with lower priority:
- answers = [
- x for x in answers if x.answer == claim or x.answer not in claims]
- break
- mjAnswers = [x for x in answers if x.answer == Message.MahJongg]
- if len(mjAnswers) > 1:
- mjPlayers = [x.player for x in mjAnswers]
- nextPlayer = self.game.nextPlayer()
- while nextPlayer not in mjPlayers:
- nextPlayer = self.game.nextPlayer(nextPlayer)
- answers = [
- x for x in answers if x.player == nextPlayer or x.answer != Message.MahJongg]
- return answers
-
- def _askForClaims(self, dummyRequests, dummyMsg):
- """ask all players if they want to claim"""
- if self.running:
- self.tellOthers(
- self.game.activePlayer,
- Message.AskForClaims,
- self.moved)
-
- def processAnswers(self, requests):
- """a player did something"""
- if not self.running:
- return
- answers = self.prioritize(requests)
- if not answers:
- return
- for answer in answers:
- msg = u'<- %s' % answer
- if Debug.traffic:
- logDebug(msg)
- with Duration(msg):
- answer.answer.serverAction(self, answer)
- return answers
-
- def moved(self, requests):
- """a player did something"""
- if Debug.stack:
- stck = traceback.extract_stack()
- if len(stck) > 30:
- logDebug(u'stack size:%d' % len(stck))
- logDebug(stck)
- answers = self.processAnswers(requests)
- if not answers:
- self.nextTurn()
-
- def tellAll(self, player, command, callback=None, **kwargs):
- """tell something about player to all players"""
- block = DeferredBlock(self)
- block.tellAll(player, command, **kwargs)
- block.callback(callback)
- return block
-
- def tellOthers(self, player, command, callback=None, **kwargs):
- """tell something about player to all other players"""
- block = DeferredBlock(self)
- block.tellOthers(player, command, **kwargs)
- block.callback(callback)
- return block
-
-
class MJServer(object):
"""the real mah jongg server"""
@@ -1030,7 +151,7 @@ class MJServer(object):
def login(self, user):
"""accept a new user"""
- if not user in self.srvUsers:
+ if user not in self.srvUsers:
self.srvUsers.append(user)
self.loadSuspendedTables(user)
@@ -1039,7 +160,6 @@ class MJServer(object):
if user.mind:
try:
args2, kwargs2 = Message.jellyAll(args, kwargs)
- # pylint: disable=star-args
return user.mind.callRemote(*args2, **kwargs2).addErrback(MJServer.ignoreLostConnection)
except (pb.DeadReferenceError, pb.PBConnectionLost):
user.mind = None
@@ -1078,7 +198,7 @@ class MJServer(object):
def ignoreLostConnection(failure):
"""if the client went away correctly, do not dump error messages on stdout."""
msg = failure.getErrorMessage()
- if not 'twisted.internet.error.ConnectionDone' in msg:
+ if 'twisted.internet.error.ConnectionDone' not in msg:
logError(msg)
failure.trap(pb.PBConnectionLost)
@@ -1119,18 +239,17 @@ class MJServer(object):
def gotRuleset(ruleset):
"""now we have the full ruleset definition from the client"""
Ruleset.cached(
- ruleset).save(
- ) # make it known to the cache and save in db
+ ruleset).save() # make it known to the cache and save in db
if tableId in self.tables:
return fail(srvError(pb.Error,
'You want a new table with id=%d but that id is already used for table %s' % (
- tableId, self.tables[tableId])))
+ tableId, self.tables[tableId])))
if Ruleset.hashIsKnown(ruleset):
return self.__newTable(None, user, ruleset, playOpen, autoPlay, wantedGame, tableId)
else:
return self.callRemote(user, 'needRuleset', ruleset).addCallback(
gotRuleset).addCallback(
- self.__newTable, user, ruleset, playOpen, autoPlay, wantedGame, tableId)
+ self.__newTable, user, ruleset, playOpen, autoPlay, wantedGame, tableId)
def __newTable(self, dummy, user, ruleset,
playOpen, autoPlay, wantedGame, tableId=None):
@@ -1295,116 +414,6 @@ class MJServer(object):
table.game = ServerGame.loadFromDB(gameid)
-class User(pb.Avatar):
-
- """the twisted avatar"""
-
- def __init__(self, userid):
- self.name = Query(
- 'select name from player where id=?',
- (userid,
- )).records[0][0]
- self.mind = None
- self.server = None
- self.dbIdent = None
- self.voiceId = None
- self.maxGameId = None
- self.lastPing = None
- self.pinged()
-
- def pinged(self):
- """time of last ping or message from user"""
- self.lastPing = datetime.datetime.now()
-
- def source(self):
- """how did he connect?"""
- result = str(self.mind.broker.transport.getPeer())
- if 'UNIXAddress' in result:
- # socket: we want to get the socket name
- result = Options.socket
- return result
-
- def attached(self, mind):
- """override pb.Avatar.attached"""
- self.mind = mind
- self.server.login(self)
-
- def detached(self, dummyMind):
- """override pb.Avatar.detached"""
- if Debug.connections:
- logDebug(
- u'%s: connection detached from %s' %
- (self, self.source()))
- self.server.logout(self)
- self.mind = None
-
- def perspective_setClientProperties(
- self, dbIdent, voiceId, maxGameId, clientVersion=None):
- """perspective_* methods are to be called remotely"""
- self.dbIdent = dbIdent
- self.voiceId = voiceId
- self.maxGameId = maxGameId
- clientVersion = nativeString(clientVersion)
- serverVersion = Internal.defaultPort
- if clientVersion != serverVersion:
- # we assume that versions x.y.* are compatible
- if clientVersion is None:
- # client passed no version info
- return fail(srvError(pb.Error,
- m18nE(
- 'Your client has a version older than 4.9.0 but you need %1 for this server'),
- serverVersion))
- else:
- commonDigits = len([x for x in zip(
- clientVersion.split(b'.'),
- serverVersion.split(b'.'))
- if x[0] == x[1]])
- if commonDigits < 2:
- return fail(srvError(pb.Error,
- m18nE(
- 'Your client has version %1 but you need %2 for this server'),
- clientVersion or '<4.9.0',
- '.'.join(serverVersion.split('.')[:2]) + '.*'))
- self.server.sendTables(self)
-
- def perspective_ping(self):
- """perspective_* methods are to be called remotely"""
- return self.pinged()
-
- def perspective_needRulesets(self, rulesetHashes):
- """perspective_* methods are to be called remotely"""
- return self.server.needRulesets(rulesetHashes)
-
- def perspective_joinTable(self, tableid):
- """perspective_* methods are to be called remotely"""
- return self.server.joinTable(self, tableid)
-
- def perspective_leaveTable(self, tableid):
- """perspective_* methods are to be called remotely"""
- return self.server.leaveTable(self, tableid)
-
- def perspective_newTable(
- self, ruleset, playOpen, autoPlay, wantedGame, tableId=None):
- """perspective_* methods are to be called remotely"""
- wantedGame = nativeString(wantedGame)
- return self.server.newTable(self, ruleset, playOpen, autoPlay, wantedGame, tableId)
-
- def perspective_startGame(self, tableid):
- """perspective_* methods are to be called remotely"""
- return self.server.startGame(self, tableid)
-
- def perspective_logout(self):
- """perspective_* methods are to be called remotely"""
- self.detached(None)
-
- def perspective_chat(self, chatString):
- """perspective_* methods are to be called remotely"""
- return self.server.chat(chatString)
-
- def __unicode__(self):
- return self.name
-
-
@implementer(portal.IRealm)
class MJRealm(object):
@@ -1415,7 +424,7 @@ class MJRealm(object):
def requestAvatar(self, avatarId, mind, *interfaces):
"""as the tutorials do..."""
- if not pb.IPerspective in interfaces:
+ if pb.IPerspective not in interfaces:
raise NotImplementedError("No supported avatar interface")
avatar = User(avatarId)
avatar.server = self.server
@@ -1432,17 +441,17 @@ def parseArgs():
defaultPort = Internal.defaultPort
parser.add_option('', '--port', dest='port',
help=m18n(
- 'the server will listen on PORT (%d)' %
- defaultPort),
+ 'the server will listen on PORT (%d)' %
+ defaultPort),
type=int, default=defaultPort)
parser.add_option('', '--socket', dest='socket',
help=m18n('the server will listen on SOCKET'), default=None)
parser.add_option(
'',
'--db',
- dest='dbpath',
- help=m18n('name of the database'),
- default=None)
+ dest='dbpath',
+ help=m18n('name of the database'),
+ default=None)
parser.add_option(
'', '--continue', dest='continueServer', action='store_true',
help=m18n('do not terminate local game server after last client disconnects'), default=False)
diff --git a/src/servercommon.py b/src/servercommon.py
new file mode 100644
index 0000000..8d72c59
--- /dev/null
+++ b/src/servercommon.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2008-2014 Wolfgang Rohdewald <wolfgang@rohdewald.de>
+
+Kajongg 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.
+
+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, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+
+from common import unicode
+from log import SERVERMARK
+
+def srvMessage(*args):
+ """
+ concatenate all args needed for m18n encoded in one string.
+ For an explanation see util.translateServerMessage.
+
+ @returns: The string to be wired.
+ @rtype: C{str}, utf-8 encoded
+ """
+ strArgs = []
+ for arg in args:
+ if isinstance(arg, unicode):
+ arg = arg.encode('utf-8')
+ else:
+ arg = str(arg).encode('utf-8')
+ strArgs.append(arg)
+ mark = SERVERMARK.encode()
+ return mark + mark.join(strArgs) + mark
+
+
+def srvError(cls, *args):
+ """raise an exception, passing args as a single string"""
+ raise cls(srvMessage(*args))
+
+
diff --git a/src/servertable.py b/src/servertable.py
new file mode 100644
index 0000000..a27eaef
--- /dev/null
+++ b/src/servertable.py
@@ -0,0 +1,895 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2009-2014 Wolfgang Rohdewald <wolfgang@rohdewald.de>
+
+Kajongg 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.
+
+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, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+The DBPasswordChecker is based on an example from the book
+Twisted Network Programming Essentials by Abe Fettig, 2006
+O'Reilly Media, Inc., ISBN 0-596-10032-9
+"""
+
+import os
+import random
+import traceback
+if os.name != 'nt':
+ import resource
+from itertools import chain
+
+from twisted.spread import pb
+
+from common import Debug, WINDS, Internal
+from util import Duration
+from message import Message, ChatMessage
+from log import logDebug, logError, m18nE, m18n, m18ncE
+from deferredutil import DeferredBlock
+from tile import Tile, TileList, elements
+from meld import Meld, MeldList
+from query import Query
+from client import Client, Table
+from wall import WallEmpty
+from sound import Voice
+from servercommon import srvError
+from user import User
+from game import PlayingGame
+
+class ServerGame(PlayingGame):
+
+ """the central game instance on the server"""
+ # pylint: disable=too-many-arguments, too-many-public-methods
+
+ def __init__(self, names, ruleset, gameid=None, wantedGame=None,
+ client=None, playOpen=False, autoPlay=False):
+ PlayingGame.__init__(
+ self,
+ names,
+ ruleset,
+ gameid,
+ wantedGame,
+ client,
+ playOpen,
+ autoPlay)
+ self.shouldSave = True
+
+ def throwDices(self):
+ """sets random living and kongBox
+ sets divideAt: an index for the wall break"""
+ self.wall.tiles.sort()
+ self.randomGenerator.shuffle(self.wall.tiles)
+ PlayingGame.throwDices(self)
+
+ def initHand(self):
+ """Happens only on server: every player gets 13 tiles (including east)"""
+ self.throwDices()
+ self.wall.divide()
+ for player in self.players:
+ player.clearHand()
+ # 13 tiles at least, with names as given by wall
+ # compensate boni
+ while len(player.concealedTiles) != 13:
+ player.addConcealedTiles(self.wall.deal())
+ PlayingGame.initHand(self)
+
+
+class ServerTable(Table):
+
+ """a table on the game server"""
+ # pylint: disable=too-many-arguments
+
+ def __init__(self, server, owner, ruleset, suspendedAt,
+ playOpen, autoPlay, wantedGame, tableId=None):
+ if tableId is None:
+ tableId = server.generateTableId()
+ Table.__init__(
+ self,
+ tableId,
+ ruleset,
+ suspendedAt,
+ False,
+ playOpen,
+ autoPlay,
+ wantedGame)
+ self.server = server
+ self.owner = owner
+ self.users = [owner] if owner else []
+ self.remotes = {} # maps client connections to users
+ self.game = None
+ self.client = None
+ server.tables[self.tableid] = self
+ if Debug.table:
+ logDebug(u'new table %s' % self)
+
+ def hasName(self, name):
+ """returns True if one of the players in the game is named 'name'"""
+ return bool(self.game) and any(x.name == name for x in self.game.players)
+
+ def asSimpleList(self, withFullRuleset=False):
+ """return the table attributes to be sent to the client"""
+ game = self.game
+ onlineNames = [x.name for x in self.users]
+ if self.suspendedAt:
+ names = tuple(x.name for x in game.players)
+ else:
+ names = tuple(x.name for x in self.users)
+ online = tuple(bool(x in onlineNames) for x in names)
+ if game:
+ endValues = game.handctr, dict(
+ (x.wind, x.balance) for x in game.players)
+ else:
+ endValues = None
+ if withFullRuleset:
+ ruleset = self.ruleset.toList()
+ else:
+ ruleset = self.ruleset.hash
+ return list(
+ [self.tableid, ruleset, game.gameid if game else None, self.suspendedAt, self.running,
+ self.playOpen, self.autoPlay, self.wantedGame, names, online, endValues])
+
+ def maxSeats(self):
+ """for a new game: 4. For a suspended game: The
+ number of humans before suspending"""
+ result = 4
+ if self.suspendedAt:
+ result -= sum(x.name.startswith(u'Robot ')
+ for x in self.game.players)
+ return result
+
+ def sendChatMessage(self, chatLine):
+ """sends a chat messages to all clients"""
+ if Debug.chat:
+ logDebug(u'server sends chat msg %s' % chatLine)
+ if self.suspendedAt:
+ chatters = []
+ for player in self.game.players:
+ chatters.extend(
+ x for x in self.server.srvUsers if x.name == player.name)
+ else:
+ chatters = self.users
+ for other in chatters:
+ self.server.callRemote(other, 'chat', chatLine.asList())
+
+ def addUser(self, user):
+ """add user to this table"""
+ if user.name in list(x.name for x in self.users):
+ raise srvError(pb.Error, m18nE('You already joined this table'))
+ if len(self.users) == self.maxSeats():
+ raise srvError(pb.Error, m18nE('All seats are already taken'))
+ self.users.append(user)
+ if Debug.table:
+ logDebug(u'%s seated on table %s' % (user.name, self))
+ self.sendChatMessage(ChatMessage(self.tableid, user.name,
+ m18nE('takes a seat'), isStatusMessage=True))
+
+ def delUser(self, user):
+ """remove user from this table"""
+ if user in self.users:
+ self.running = False
+ self.users.remove(user)
+ self.sendChatMessage(ChatMessage(self.tableid, user.name,
+ m18nE('leaves the table'), isStatusMessage=True))
+ if user is self.owner:
+ # silently pass ownership
+ if self.users:
+ self.owner = self.users[0]
+ if Debug.table:
+ logDebug(u'%s leaves table %d, %s is the new owner' % (
+ user.name, self.tableid, self.owner))
+ else:
+ if Debug.table:
+ logDebug(u'%s leaves table %d, table is now empty' % (
+ user.name, self.tableid))
+ else:
+ if Debug.table:
+ logDebug(u'%s leaves table %d, %s stays owner' % (
+ user.name, self.tableid, self.owner))
+
+ def __unicode__(self):
+ """for debugging output"""
+ onlineNames = list(x.name + (u'(Owner)' if x == self.owner.name else u'')
+ for x in self.users)
+ offlineString = u''
+ if self.game:
+ offlineNames = list(x.name for x in self.game.players if x.name not in onlineNames
+ and not x.name.startswith(u'Robot'))
+ if offlineNames:
+ offlineString = u' offline:' + u','.join(offlineNames)
+ return u'%d(%s%s)' % (self.tableid, u','.join(onlineNames), offlineString)
+
+ def calcGameId(self):
+ """based upon the max gameids we got from the clients, propose
+ a new one, we want to use the same gameid in all data bases"""
+ serverMaxGameId = Query('select max(id) from game').records[0][0]
+ serverMaxGameId = int(serverMaxGameId) if serverMaxGameId else 0
+ gameIds = [x.maxGameId for x in self.users]
+ gameIds.append(serverMaxGameId)
+ return max(gameIds) + 1
+
+ def __prepareNewGame(self):
+ """returns a new game object"""
+ names = list(x.name for x in self.users)
+ # the server and all databases save the english name but we
+ # want to make sure a translation exists for the client GUI
+ robotNames = [
+ m18ncE(
+ 'kajongg, name of robot player, to be translated',
+ u'Robot 1'),
+ m18ncE(
+ 'kajongg, name of robot player, to be translated',
+ u'Robot 2'),
+ m18ncE('kajongg, name of robot player, to be translated', u'Robot 3')]
+ while len(names) < 4:
+ names.append(robotNames[3 - len(names)])
+ names = list(tuple([WINDS[idx], name])
+ for idx, name in enumerate(names))
+ self.client = Client()
+ # Game has a weakref to client, so we must keep
+ # it!
+ return ServerGame(names, self.ruleset, client=self.client,
+ playOpen=self.playOpen, autoPlay=self.autoPlay, wantedGame=self.wantedGame)
+
+ def userForPlayer(self, player):
+ """finds the table user corresponding to player"""
+ for result in self.users:
+ if result.name == player.name:
+ return result
+
+ def __connectPlayers(self):
+ """connects client instances with the game players"""
+ game = self.game
+ for player in game.players:
+ remote = self.userForPlayer(player)
+ if not remote:
+ # we found a robot player, its client runs in this server
+ # process
+ remote = Client(player.name)
+ remote.table = self
+ self.remotes[player] = remote
+
+ def __checkDbIdents(self):
+ """for 4 players, we have up to 4 data bases:
+ more than one player might use the same data base.
+ However the server always needs to use its own data base.
+ If a data base is used by more than one client, only one of
+ them should update. Here we set shouldSave for all players,
+ while the server always saves"""
+ serverIdent = Internal.db.identifier
+ dbIdents = set()
+ game = self.game
+ for player in game.players:
+ player.shouldSave = False
+ if isinstance(self.remotes[player], User):
+ dbIdent = self.remotes[player].dbIdent
+ assert dbIdent != serverIdent, \
+ 'client and server try to use the same database:%s' % \
+ Internal.db.path
+ player.shouldSave = dbIdent not in dbIdents
+ dbIdents.add(dbIdent)
+
+ def readyForGameStart(self, user):
+ """the table initiator told us he wants to start the game"""
+ if len(self.users) < self.maxSeats() and self.owner != user:
+ raise srvError(pb.Error,
+ m18nE(
+ 'Only the initiator %1 can start this game, you are %2'),
+ self.owner.name, user.name)
+ if self.suspendedAt:
+ self.__connectPlayers()
+ self.__checkDbIdents()
+ self.initGame()
+ else:
+ self.game = self.__prepareNewGame()
+ self.__connectPlayers()
+ self.__checkDbIdents()
+ self.proposeGameId(self.calcGameId())
+ # TODO: remove table for all other srvUsers out of sight
+
+ def proposeGameId(self, gameid):
+ """server proposes an id to the clients ands waits for answers"""
+ while True:
+ query = Query('insert into game(id,seed) values(?,?)',
+ (gameid, 'proposed'), mayFail=True, failSilent=True)
+ if not query.failure:
+ break
+ gameid += random.randrange(1, 100)
+ block = DeferredBlock(self)
+ for player in self.game.players:
+ if player.shouldSave and isinstance(self.remotes[player], User):
+ # do not ask robot players, they use the server data base
+ block.tellPlayer(player, Message.ProposeGameId, gameid=gameid)
+ block.callback(self.collectGameIdAnswers, gameid)
+
+ def collectGameIdAnswers(self, requests, gameid):
+ """clients answered if the proposed game id is free"""
+ if requests:
+ # when errors happen, there might be no requests left
+ for msg in requests:
+ if msg.answer == Message.NO:
+ self.proposeGameId(gameid + 1)
+ return
+ elif msg.answer != Message.OK:
+ raise srvError(
+ pb.Error,
+ 'collectGameIdAnswers got neither NO nor OK')
+ self.game.gameid = gameid
+ self.initGame()
+
+ def initGame(self):
+ """ask clients if they are ready to start"""
+ game = self.game
+ game.saveStartTime()
+ block = DeferredBlock(self)
+ for player in game.players:
+ block.tellPlayer(
+ player, Message.ReadyForGameStart, tableid=self.tableid,
+ gameid=game.gameid, shouldSave=player.shouldSave,
+ wantedGame=game.wantedGame, source=list((x.wind, x.name) for x in game.players))
+ block.callback(self.startGame)
+
+ def startGame(self, requests):
+ """if all players said ready, start the game"""
+ for user in self.users:
+ userRequests = list(x for x in requests if x.user == user)
+ if not userRequests or userRequests[0].answer == Message.NoGameStart:
+ if Debug.table:
+ if not userRequests:
+ logDebug(
+ u'Server.startGame: found no request for user %s' %
+ user.name)
+ else:
+ logDebug(
+ u'Server.startGame: %s said NoGameStart' %
+ user.name)
+ self.game = None
+ return
+ if Debug.table:
+ logDebug(u'Game starts on table %s' % self)
+ elementIter = iter(elements.all(self.game.ruleset))
+ wallSize = len(self.game.wall.tiles)
+ self.game.wall.tiles = []
+ for _ in range(wallSize):
+ self.game.wall.tiles.append(next(elementIter).concealed)
+ assert isinstance(self.game, ServerGame), self.game
+ self.running = True
+ self.__adaptOtherTables()
+ self.sendVoiceIds()
+
+ def __adaptOtherTables(self):
+ """if the players on this table also reserved seats on other tables, clear them
+ make running table invisible for other users"""
+ for user in self.users:
+ for tableid in self.server.tablesWith(user):
+ if tableid != self.tableid:
+ self.server.leaveTable(user, tableid)
+ foreigners = list(
+ x for x in self.server.srvUsers if x not in self.users)
+ if foreigners:
+ if Debug.table:
+ logDebug(
+ u'make running table %s invisible for %s' %
+ (self, ','.join(str(x) for x in foreigners)))
+ for srvUser in foreigners:
+ self.server.callRemote(
+ srvUser,
+ 'tableRemoved',
+ self.tableid,
+ '')
+
+ def sendVoiceIds(self):
+ """tell each player what voice ids the others have. By now the client has a Game instance!"""
+ humanPlayers = [
+ x for x in self.game.players if isinstance(self.remotes[x], User)]
+ if len(humanPlayers) < 2 or not any(self.remotes[x].voiceId for x in humanPlayers):
+ # no need to pass around voice data
+ self.assignVoices()
+ return
+ block = DeferredBlock(self)
+ for player in humanPlayers:
+ remote = self.remotes[player]
+ if remote.voiceId:
+ # send it to other human players:
+ others = [x for x in humanPlayers if x != player]
+ if Debug.sound:
+ logDebug(u'telling other human players that %s has voiceId %s' % (
+ player.name, remote.voiceId))
+ block.tell(
+ player,
+ others,
+ Message.VoiceId,
+ source=remote.voiceId)
+ block.callback(self.collectVoiceData)
+
+ def collectVoiceData(self, requests):
+ """collect voices of other players"""
+ if not self.running:
+ return
+ block = DeferredBlock(self)
+ voiceDataRequests = []
+ for request in requests:
+ if request.answer == Message.ClientWantsVoiceData:
+ # another human player requests sounds for voiceId
+ voiceId = request.args[0]
+ voiceFor = [x for x in self.game.players if isinstance(self.remotes[x], User)
+ and self.remotes[x].voiceId == voiceId][0]
+ voiceFor.voice = Voice(voiceId)
+ if Debug.sound:
+ logDebug(
+ u'client %s wants voice data %s for %s' %
+ (request.user.name, request.args[0], voiceFor))
+ voiceDataRequests.append((request.user, voiceFor))
+ if not voiceFor.voice.oggFiles():
+ # the server does not have it, ask the client with that
+ # voice
+ block.tell(
+ voiceFor,
+ voiceFor,
+ Message.ServerWantsVoiceData)
+ block.callback(self.sendVoiceData, voiceDataRequests)
+
+ def sendVoiceData(self, requests, voiceDataRequests):
+ """sends voice sounds to other human players"""
+ self.processAnswers(requests)
+ block = DeferredBlock(self)
+ for voiceDataRequester, voiceFor in voiceDataRequests:
+ # this player requested sounds for voiceFor
+ voice = voiceFor.voice
+ content = voice.archiveContent
+ if content:
+ if Debug.sound:
+ logDebug(
+ u'server got voice data %s for %s from client' %
+ (voiceFor.voice, voiceFor.name))
+ block.tell(
+ voiceFor,
+ voiceDataRequester,
+ Message.VoiceData,
+ md5sum=voice.md5sum,
+ source=content)
+ elif Debug.sound:
+ logDebug(u'server got empty voice data %s for %s from client' % (
+ voice, voiceFor.name))
+ block.callback(self.assignVoices)
+
+ def assignVoices(self, dummyResults=None):
+ """now all human players have all voice data needed"""
+ humanPlayers = [
+ x for x in self.game.players if isinstance(self.remotes[x], User)]
+ block = DeferredBlock(self)
+ block.tell(None, humanPlayers, Message.AssignVoices)
+ block.callback(self.startHand)
+
+ def pickTile(self, dummyResults=None, deadEnd=False):
+ """the active player gets a tile from wall. Tell all clients."""
+ if not self.running:
+ return
+ player = self.game.activePlayer
+ try:
+ tile = player.pickedTile(deadEnd)
+ except WallEmpty:
+ self.endHand()
+ else:
+ self.game.lastDiscard = None
+ block = DeferredBlock(self)
+ block.tellPlayer(
+ player,
+ Message.PickedTile,
+ tile=tile,
+ deadEnd=deadEnd)
+ showTile = tile if tile.isBonus or self.game.playOpen else Tile.unknown
+ block.tellOthers(
+ player,
+ Message.PickedTile,
+ tile=showTile,
+ deadEnd=deadEnd)
+ block.callback(self.moved)
+
+ def pickKongReplacement(self, requests=None):
+ """the active player gets a tile from the dead end. Tell all clients."""
+ requests = self.prioritize(requests)
+ if requests and requests[0].answer == Message.MahJongg:
+ # somebody robs my kong
+ requests[0].answer.serverAction(self, requests[0])
+ else:
+ self.pickTile(requests, deadEnd=True)
+
+ def clientDiscarded(self, msg):
+ """client told us he discarded a tile. Check for consistency and tell others."""
+ if not self.running:
+ return
+ player = msg.player
+ assert player == self.game.activePlayer
+ tile = Tile(msg.args[0])
+ if tile not in player.concealedTiles:
+ self.abort(
+ u'player %s discarded %s but does not have it' %
+ (player, tile))
+ return
+ dangerousText = self.game.dangerousFor(player, tile)
+ mustPlayDangerous = player.mustPlayDangerous()
+ violates = player.violatesOriginalCall(tile)
+ self.game.hasDiscarded(player, tile)
+ block = DeferredBlock(self)
+ block.tellAll(player, Message.Discard, tile=tile)
+ block.callback(self._clientDiscarded2, msg, dangerousText, mustPlayDangerous, violates)
+
+ def _clientDiscarded2(self, dummyResults, msg, dangerousText, mustPlayDangerous, violates):
+ """client told us he discarded a tile. Continue, check for calling"""
+ block = DeferredBlock(self)
+ player = msg.player
+ if violates:
+ if Debug.originalCall:
+ tile = Tile(msg.args[0])
+ logDebug(u'%s just violated OC with %s' % (player, tile))
+ player.mayWin = False
+ block.tellAll(player, Message.ViolatesOriginalCall)
+ block.callback(self._clientDiscarded3, msg, dangerousText, mustPlayDangerous)
+
+ def _clientDiscarded3(self, dummyResults, msg, dangerousText, mustPlayDangerous):
+ """client told us he discarded a tile. Continue, check for calling"""
+ block = DeferredBlock(self)
+ player = msg.player
+ if self.game.ruleset.mustDeclareCallingHand and not player.isCalling:
+ if player.hand.callingHands:
+ player.isCalling = True
+ block.tellAll(player, Message.Calling)
+ block.callback(self._clientDiscarded4, msg, dangerousText, mustPlayDangerous)
+
+ def _clientDiscarded4(self, dummyResults, msg, dangerousText, mustPlayDangerous):
+ """client told us he discarded a tile. Continue, check for dangerous game"""
+ block = DeferredBlock(self)
+ player = msg.player
+ if dangerousText:
+ if mustPlayDangerous and player.lastSource not in 'dZ':
+ if Debug.dangerousGame:
+ tile = Tile(msg.args[0])
+ logDebug(u'%s claims no choice. Discarded %s, keeping %s. %s' %
+ (player, tile, ''.join(player.concealedTiles), ' / '.join(dangerousText)))
+ player.claimedNoChoice = True
+ block.tellAll(
+ player,
+ Message.NoChoice,
+ tiles=TileList(player.concealedTiles))
+ else:
+ player.playedDangerous = True
+ if Debug.dangerousGame:
+ tile = Tile(msg.args[0])
+ logDebug(u'%s played dangerous. Discarded %s, keeping %s. %s' %
+ (player, tile, ''.join(player.concealedTiles), ' / '.join(dangerousText)))
+ block.tellAll(
+ player,
+ Message.DangerousGame,
+ tiles=TileList(player.concealedTiles))
+ if msg.answer == Message.OriginalCall:
+ player.isCalling = True
+ block.callback(self.clientMadeOriginalCall, msg)
+ else:
+ block.callback(self._askForClaims, msg)
+
+ def clientMadeOriginalCall(self, dummyResults, msg):
+ """first tell everybody about original call
+ and then treat the implicit discard"""
+ msg.player.originalCall = True
+ if Debug.originalCall:
+ logDebug(u'server.clientMadeOriginalCall: %s' % msg.player)
+ block = DeferredBlock(self)
+ block.tellAll(msg.player, Message.OriginalCall)
+ block.callback(self._askForClaims, msg)
+
+ def startHand(self, dummyResults=None):
+ """all players are ready to start a hand, so do it"""
+ if self.running:
+ self.game.prepareHand()
+ self.game.initHand()
+ block = self.tellAll(None, Message.InitHand,
+ divideAt=self.game.divideAt)
+ block.callback(self.divided)
+
+ def divided(self, dummyResults=None):
+ """the wall is now divided for all clients"""
+ if not self.running:
+ return
+ block = DeferredBlock(self)
+ for clientPlayer in self.game.players:
+ for player in self.game.players:
+ if player == clientPlayer or self.game.playOpen:
+ tiles = player.concealedTiles
+ else:
+ tiles = TileList(Tile.unknown * 13)
+ block.tell(player, clientPlayer, Message.SetConcealedTiles,
+ tiles=TileList(chain(tiles, player.bonusTiles)))
+ block.callback(self.dealt)
+
+ def endHand(self, dummyResults=None):
+ """hand is over, show all concealed tiles to all players"""
+ if not self.running:
+ return
+ if self.game.playOpen:
+ self.saveHand()
+ else:
+ block = DeferredBlock(self)
+ for player in self.game.players:
+ # there might be no winner, winner.others() would be wrong
+ if player != self.game.winner:
+ # the winner tiles are already shown in claimMahJongg
+ block.tellOthers(
+ player, Message.ShowConcealedTiles, show=True,
+ tiles=TileList(player.concealedTiles))
+ block.callback(self.saveHand)
+
+ def saveHand(self, dummyResults=None):
+ """save the hand to the database and proceed to next hand"""
+ if not self.running:
+ return
+ self.tellAll(None, Message.SaveHand, self.nextHand)
+ self.game.saveHand()
+
+ def nextHand(self, dummyResults):
+ """next hand: maybe rotate"""
+ if not self.running:
+ return
+ DeferredBlock.garbageCollection()
+ for block in DeferredBlock.blocks:
+ if block.table == self:
+ logError(
+ u'request left from previous hand: %s' %
+ block.outstandingUnicode())
+ token = self.game.handId.prompt(
+ withAI=False) # we need to send the old token until the
+ # clients started the new hand
+ rotateWinds = self.game.maybeRotateWinds()
+ if self.game.finished():
+ self.server.removeTable(
+ self,
+ 'gameOver',
+ m18nE('Game <numid>%1</numid> is over!'),
+ self.game.seed)
+ if Debug.process and os.name != 'nt':
+ logDebug(
+ u'MEM:%s' %
+ resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
+ return
+ self.game.sortPlayers()
+ playerNames = list((x.wind, x.name) for x in self.game.players)
+ self.tellAll(None, Message.ReadyForHandStart, self.startHand,
+ source=playerNames, rotateWinds=rotateWinds, token=token)
+
+ def abort(self, message, *args):
+ """abort the table. Reason: message/args"""
+ self.server.removeTable(self, 'abort', message, *args)
+
+ def claimTile(self, player, claim, meldTiles, nextMessage):
+ """a player claims a tile for pung, kong or chow.
+ meldTiles contains the claimed tile, concealed"""
+ if not self.running:
+ return
+ lastDiscard = self.game.lastDiscard
+ # if we rob a tile, self.game.lastDiscard has already been set to the
+ # robbed tile
+ hasTiles = Meld(meldTiles[:])
+ discardingPlayer = self.game.activePlayer
+ hasTiles = hasTiles.without(lastDiscard)
+ meld = Meld(meldTiles)
+ if len(meld) != 4 and not (meld.isPair or meld.isPungKong or meld.isChow):
+ msg = m18nE('%1 wrongly said %2 for meld %3')
+ self.abort(msg, player.name, claim.name, str(meld))
+ return
+ if not player.hasConcealedTiles(hasTiles):
+ msg = m18nE(
+ '%1 wrongly said %2: claims to have concealed tiles %3 but only has %4')
+ self.abort(
+ msg,
+ player.name,
+ claim.name,
+ ' '.join(hasTiles),
+ ''.join(player.concealedTiles))
+ return
+ # update our internal state before we listen to the clients again
+ self.game.discardedTiles[lastDiscard.exposed] -= 1
+ self.game.activePlayer = player
+ if lastDiscard:
+ player.lastTile = lastDiscard.exposed
+ player.lastSource = 'd'
+ player.exposeMeld(hasTiles, lastDiscard)
+ self.game.lastDiscard = None
+ block = DeferredBlock(self)
+ if (nextMessage != Message.Kong
+ and self.game.dangerousFor(discardingPlayer, lastDiscard)
+ and discardingPlayer.playedDangerous):
+ player.usedDangerousFrom = discardingPlayer
+ if Debug.dangerousGame:
+ logDebug(u'%s claims dangerous tile %s discarded by %s' %
+ (player, lastDiscard, discardingPlayer))
+ block.tellAll(
+ player,
+ Message.UsedDangerousFrom,
+ source=discardingPlayer.name)
+ block.tellAll(player, nextMessage, meld=meld)
+ if claim == Message.Kong:
+ block.callback(self.pickKongReplacement)
+ else:
+ block.callback(self.moved)
+
+ def declareKong(self, player, meldTiles):
+ """player declares a Kong, meldTiles is a list"""
+ kongMeld = Meld(meldTiles)
+ if not player.hasConcealedTiles(kongMeld) and kongMeld[0].exposed.pung not in player.exposedMelds:
+ msg = m18nE('declareKong:%1 wrongly said Kong for meld %2')
+ args = (player.name, str(kongMeld))
+ logDebug(m18n(msg, *args))
+ logDebug(
+ u'declareKong:concealedTiles:%s' %
+ ''.join(player.concealedTiles))
+ logDebug(u'declareKong:concealedMelds:%s' %
+ ' '.join(str(x) for x in player.concealedMelds))
+ logDebug(u'declareKong:exposedMelds:%s' %
+ ' '.join(str(x) for x in player.exposedMelds))
+ self.abort(msg, *args)
+ return
+ player.exposeMeld(kongMeld)
+ self.tellAll(
+ player,
+ Message.DeclaredKong,
+ self.pickKongReplacement,
+ meld=kongMeld)
+
+ def claimMahJongg(self, msg):
+ """a player claims mah jongg. Check this and
+ if correct, tell all. Otherwise abort game, kajongg client is faulty"""
+ if not self.running:
+ return
+ player = msg.player
+ concealedMelds = MeldList(msg.args[0])
+ withDiscard = Tile(msg.args[1]) if msg.args[1] else None
+ lastMeld = Meld(msg.args[2])
+ if self.game.ruleset.mustDeclareCallingHand:
+ assert player.isCalling, '%s %s %s says MJ but never claimed: concmelds:%s withdiscard:%s lastmeld:%s' % (
+ self.game.handId, player.hand, player, concealedMelds, withDiscard, lastMeld)
+ discardingPlayer = self.game.activePlayer
+ lastMove = next(self.game.lastMoves(withoutNotifications=True))
+ robbedTheKong = lastMove.message == Message.DeclaredKong
+ if robbedTheKong:
+ player.robsTile()
+ withDiscard = lastMove.meld[0].concealed
+ lastMove.player.robTileFrom(withDiscard)
+ msgArgs = player.showConcealedMelds(concealedMelds, withDiscard)
+ if msgArgs:
+ self.abort(*msgArgs)
+ return
+ player.declaredMahJongg(
+ concealedMelds,
+ withDiscard,
+ player.lastTile,
+ lastMeld)
+ if not player.hand.won:
+ msg = m18nE('%1 claiming MahJongg: This is not a winning hand: %2')
+ self.abort(msg, player.name, player.hand.string)
+ return
+ block = DeferredBlock(self)
+ if robbedTheKong:
+ block.tellAll(player, Message.RobbedTheKong, tile=withDiscard)
+ if (player.lastSource == 'd'
+ and self.game.dangerousFor(discardingPlayer, player.lastTile)
+ and discardingPlayer.playedDangerous):
+ player.usedDangerousFrom = discardingPlayer
+ if Debug.dangerousGame:
+ logDebug(u'%s wins with dangerous tile %s from %s' %
+ (player, self.game.lastDiscard, discardingPlayer))
+ block.tellAll(
+ player,
+ Message.UsedDangerousFrom,
+ source=discardingPlayer.name)
+ block.tellAll(
+ player, Message.MahJongg, melds=concealedMelds, lastTile=player.lastTile,
+ lastMeld=lastMeld, withDiscardTile=withDiscard)
+ block.callback(self.endHand)
+
+ def dealt(self, dummyResults):
+ """all tiles are dealt, ask east to discard a tile"""
+ if self.running:
+ self.tellAll(
+ self.game.activePlayer,
+ Message.ActivePlayer,
+ self.pickTile)
+
+ def nextTurn(self):
+ """the next player becomes active"""
+ if self.running:
+ # the player might just have disconnected
+ self.game.nextTurn()
+ self.tellAll(
+ self.game.activePlayer,
+ Message.ActivePlayer,
+ self.pickTile)
+
+ def prioritize(self, requests):
+ """returns only requests we want to execute"""
+ if not self.running:
+ return
+ answers = [
+ x for x in requests if x.answer not in [
+ Message.NoClaim,
+ Message.OK,
+ None]]
+ if len(answers) > 1:
+ claims = [
+ Message.MahJongg,
+ Message.Kong,
+ Message.Pung,
+ Message.Chow]
+ for claim in claims:
+ if claim in [x.answer for x in answers]:
+ # ignore claims with lower priority:
+ answers = [
+ x for x in answers if x.answer == claim or x.answer not in claims]
+ break
+ mjAnswers = [x for x in answers if x.answer == Message.MahJongg]
+ if len(mjAnswers) > 1:
+ mjPlayers = [x.player for x in mjAnswers]
+ nextPlayer = self.game.nextPlayer()
+ while nextPlayer not in mjPlayers:
+ nextPlayer = self.game.nextPlayer(nextPlayer)
+ answers = [
+ x for x in answers if x.player == nextPlayer or x.answer != Message.MahJongg]
+ return answers
+
+ def _askForClaims(self, dummyRequests, dummyMsg):
+ """ask all players if they want to claim"""
+ if self.running:
+ self.tellOthers(
+ self.game.activePlayer,
+ Message.AskForClaims,
+ self.moved)
+
+ def processAnswers(self, requests):
+ """a player did something"""
+ if not self.running:
+ return
+ answers = self.prioritize(requests)
+ if not answers:
+ return
+ for answer in answers:
+ msg = u'<- %s' % answer
+ if Debug.traffic:
+ logDebug(msg)
+ with Duration(msg):
+ answer.answer.serverAction(self, answer)
+ return answers
+
+ def moved(self, requests):
+ """a player did something"""
+ if Debug.stack:
+ stck = traceback.extract_stack()
+ if len(stck) > 30:
+ logDebug(u'stack size:%d' % len(stck))
+ logDebug(stck)
+ answers = self.processAnswers(requests)
+ if not answers:
+ self.nextTurn()
+
+ def tellAll(self, player, command, callback=None, **kwargs):
+ """tell something about player to all players"""
+ block = DeferredBlock(self)
+ block.tellAll(player, command, **kwargs)
+ block.callback(callback)
+ return block
+
+ def tellOthers(self, player, command, callback=None, **kwargs):
+ """tell something about player to all other players"""
+ block = DeferredBlock(self)
+ block.tellOthers(player, command, **kwargs)
+ block.callback(callback)
+ return block
+
diff --git a/src/setup.py b/src/setup.py
index 628ac52..41a17ea 100644
--- a/src/setup.py
+++ b/src/setup.py
@@ -26,6 +26,8 @@ on linux in the src directory with Kajongg fully installed.
Usage: see ../README.windows
"""
+# pylint: disable=wrong-import-order, wrong-import-position, import-error
+
# ==== adapt this part =====
FULLAUTHOR = "Wolfgang Rohdewald <wolfgang@rohdewald.de>"
LICENSE = 'GNU General Public License v2'
@@ -49,9 +51,9 @@ if os.path.exists('build'):
includes = [
"zope.interface",
- "twisted.internet",
- "twisted.internet.protocol",
- "pkg_resources"]
+ "twisted.internet",
+ "twisted.internet.protocol",
+ "pkg_resources"]
packages = []
namespace_packages = ["zope"]
include_files = ('share',)
@@ -105,8 +107,8 @@ setup(
version=VERSION,
description='The classical game of Mah Jongg',
long_description="This is the classical Mah Jongg for four players. "
- "If you are looking for the Mah Jongg solitaire please use the "
- "application kmahjongg.",
+ "If you are looking for the Mah Jongg solitaire please use the "
+ "application kmahjongg.",
author=AUTHOR,
author_email=EMAIL,
url=URL,
diff --git a/src/sound.py b/src/sound.py
index ee578b3..b5f9237 100644
--- a/src/sound.py
+++ b/src/sound.py
@@ -56,7 +56,7 @@ class Sound(object):
@staticmethod
def findOgg():
- """sets __oggName to exe name or False"""
+ """sets __oggName to exe name or an empty string"""
if Sound.__oggName is None:
if os.name == 'nt':
parentDirectories = KGlobal.dirs().findDirs(
@@ -76,7 +76,7 @@ class Sound(object):
if which(oggName):
Sound.__oggName = oggName
else:
- Sound.__oggName = False
+ Sound.__oggName = ''
Internal.Preferences.useSounds = False
# checks again at next reenable
if msg:
@@ -161,9 +161,9 @@ class Sound(object):
Sound.playProcesses.append(process)
reactor.callLater(3, Sound.cleanProcesses)
reactor.callLater(6, Sound.cleanProcesses)
- elif False:
- text = os.path.basename(what)
- text = os.path.splitext(text)[0]
+# else:
+# text = os.path.basename(what)
+# text = os.path.splitext(text)[0]
# If this ever works, we need to translate all texts
# we need package jovie and mbrola voices
# KSpeech setLanguage de
@@ -174,11 +174,11 @@ class Sound(object):
# with that name exists
# getTalkerCodes returns nothing
# this all feels immature
- if len(text) == 2 and text[0] in 'sdbcw':
- text = Tile(text).name()
- args = ['qdbus', 'org.kde.jovie',
- '/KSpeech', 'say', text, '1']
- subprocess.Popen(args)
+# if len(text) == 2 and text[0] in 'sdbcw':
+# text = Tile(text).name()
+# args = ['qdbus', 'org.kde.jovie',
+# '/KSpeech', 'say', text, '1']
+# subprocess.Popen(args)
class Voice(object):
@@ -354,7 +354,8 @@ class Voice(object):
# print(self.directory, self.__md5sum)
existingMd5sum = self.savedmd5Sum()
md5Name = self.md5FileName()
- if False: # TODO: warum ist das in python3 unterschiedlich? self.__md5sum != existingMd5sum:
+ if False: # pylint: disable=using-constant-test
+ # TODO: warum ist das in python3 unterschiedlich? self.__md5sum != existingMd5sum:
if Debug.sound:
if not os.path.exists(md5Name):
logDebug(u'creating new %s' % md5Name)
diff --git a/src/tables.py b/src/tables.py
index 7642613..cb07104 100644
--- a/src/tables.py
+++ b/src/tables.py
@@ -88,7 +88,7 @@ class TablesModel(QAbstractTableModel):
def data(self, index, role=Qt.DisplayRole):
"""score table"""
- # pylint: disable=too-many-branches,too-many-locals
+ # pylint: disable=too-many-branches,too-many-locals,redefined-variable-type
result = toQVariant()
if role == Qt.TextAlignmentRole:
if index.column() == 0:
diff --git a/src/tile.py b/src/tile.py
index 79e5f40..5ded252 100644
--- a/src/tile.py
+++ b/src/tile.py
@@ -43,8 +43,7 @@ class Tile(str):
value is 1..9 for real suit tiles, -1/0/10/11 for usage in AI, and a char
for dragons, winds,boni
"""
- # pylint: disable=too-many-public-methods, abstract-class-not-used,
- # too-many-instance-attributes
+ # pylint: disable=too-many-public-methods,too-many-instance-attributes
cache = {}
hashTable = 'XyxyDbdbDgdgDrdrWeweWswsWw//wwWnwn' \
'S/s/S0s0S1s1S2s2S3s3S4s4S5s5S6s6S7s7S8s8S9s9S:s:S;s;' \
diff --git a/src/tileset.py b/src/tileset.py
index 7a01463..2bbe830 100644
--- a/src/tileset.py
+++ b/src/tileset.py
@@ -77,13 +77,14 @@ class Tileset(object):
# we want default to be first in list
sortedTilesets = ['default']
sortedTilesets.extend(tilesets - set(['default']))
- tilesets = sortedTilesets
+ tilesets = set(sortedTilesets)
for dontWant in ['alphabet', 'egypt']:
if dontWant in tilesets:
tilesets.remove(dontWant)
return [Tileset(x) for x in tilesets]
- def __noTilesetFound(self):
+ @staticmethod
+ def __noTilesetFound():
"""No tilesets found"""
directories = '\n'.join(
str(x) for x in KGlobal.dirs().resourceDirs("kmahjonggtileset"))
@@ -105,7 +106,7 @@ class Tileset(object):
if self.path.isEmpty():
self.path = locateTileset('default.desktop')
if self.path.isEmpty():
- self.__noTilesetsFound()
+ self.__noTilesetFound()
else:
logWarning(
m18n(
@@ -162,6 +163,7 @@ class Tileset(object):
@staticmethod
def activeTileset():
+ """the currently wanted tileset. If not yet defined, do so"""
prefName = Internal.Preferences.tilesetName
if (not Tileset.__activeTileset
or Tileset.__activeTileset.desktopFileName != prefName):
diff --git a/src/tilesetselector.py b/src/tilesetselector.py
index b703f41..96576f7 100644
--- a/src/tilesetselector.py
+++ b/src/tilesetselector.py
@@ -53,7 +53,6 @@ class TilesetSelector(QWidget):
self.tileView.setParent(self.tilesetPreview)
layout = QHBoxLayout(self.tilesetPreview)
layout.addWidget(self.tileView)
- # pylint: disable=star-args
for idx, offsets in enumerate([(0, 0), (0, 1), (1, 0), (1, 1)]):
self.uiTiles[idx].setBoard(
self.board,
diff --git a/src/tree.py b/src/tree.py
index 1cbb4ef..94ea4b8 100644
--- a/src/tree.py
+++ b/src/tree.py
@@ -25,7 +25,7 @@ Here we define classes useful for tree views
from qt import QAbstractItemModel, QModelIndex
-class TreeItem(object): # pylint: disable=abstract-class-little-used
+class TreeItem(object):
"""generic class for items in a tree"""
diff --git a/src/uiwall.py b/src/uiwall.py
index c53eeee..4b822e9 100644
--- a/src/uiwall.py
+++ b/src/uiwall.py
@@ -244,7 +244,7 @@ class UIWall(Wall):
for side in self.__sides:
side.nameLabel.setFont(font)
- def showShadowsChanged(self, oldValue, newValue):
+ def showShadowsChanged(self, dummyOldValue, dummyNewValue):
"""setting this actually changes the visuals."""
assert ParallelAnimationGroup.current is None
self.__resizeHandBoards()
@@ -277,7 +277,7 @@ class UIWall(Wall):
uiTile.update()
@afterQueuedAnimations
- def _placeLooseTiles(self, deferredResult):
+ def _placeLooseTiles(self, deferredResult=None):
"""place the last 2 tiles on top of kong box"""
assert len(self.kongBox) % 2 == 0
placeCount = len(self.kongBox) // 2
diff --git a/src/user.py b/src/user.py
new file mode 100644
index 0000000..260b41d
--- /dev/null
+++ b/src/user.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2009-2014 Wolfgang Rohdewald <wolfgang@rohdewald.de>
+
+Kajongg 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.
+
+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, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+The DBPasswordChecker is based on an example from the book
+Twisted Network Programming Essentials by Abe Fettig, 2006
+O'Reilly Media, Inc., ISBN 0-596-10032-9
+"""
+
+import datetime
+
+from twisted.internet.defer import fail
+from twisted.spread import pb
+
+from common import Internal, Debug, Options, nativeString
+from servercommon import srvError
+from log import logDebug, m18nE
+from query import Query
+
+class User(pb.Avatar):
+
+ """the twisted avatar"""
+
+ def __init__(self, userid):
+ self.name = Query(
+ 'select name from player where id=?',
+ (userid,
+ )).records[0][0]
+ self.mind = None
+ self.server = None
+ self.dbIdent = None
+ self.voiceId = None
+ self.maxGameId = None
+ self.lastPing = None
+ self.pinged()
+
+ def pinged(self):
+ """time of last ping or message from user"""
+ self.lastPing = datetime.datetime.now()
+
+ def source(self):
+ """how did he connect?"""
+ result = str(self.mind.broker.transport.getPeer())
+ if 'UNIXAddress' in result:
+ # socket: we want to get the socket name
+ result = Options.socket
+ return result
+
+ def attached(self, mind):
+ """override pb.Avatar.attached"""
+ self.mind = mind
+ self.server.login(self)
+
+ def detached(self, dummyMind):
+ """override pb.Avatar.detached"""
+ if Debug.connections:
+ logDebug(
+ u'%s: connection detached from %s' %
+ (self, self.source()))
+ self.server.logout(self)
+ self.mind = None
+
+ def perspective_setClientProperties(
+ self, dbIdent, voiceId, maxGameId, clientVersion=None):
+ """perspective_* methods are to be called remotely"""
+ self.dbIdent = dbIdent
+ self.voiceId = voiceId
+ self.maxGameId = maxGameId
+ clientVersion = nativeString(clientVersion)
+ serverVersion = Internal.defaultPort
+ if clientVersion != serverVersion:
+ # we assume that versions x.y.* are compatible
+ if clientVersion is None:
+ # client passed no version info
+ return fail(srvError(pb.Error,
+ m18nE(
+ 'Your client has a version older than 4.9.0 but you need %1 for this server'),
+ serverVersion))
+ else:
+ commonDigits = len([x for x in zip(
+ clientVersion.split(b'.'),
+ serverVersion.split(b'.'))
+ if x[0] == x[1]])
+ if commonDigits < 2:
+ return fail(srvError(pb.Error,
+ m18nE(
+ 'Your client has version %1 but you need %2 for this server'),
+ clientVersion or '<4.9.0',
+ '.'.join(serverVersion.split('.')[:2]) + '.*'))
+ self.server.sendTables(self)
+
+ def perspective_ping(self):
+ """perspective_* methods are to be called remotely"""
+ return self.pinged()
+
+ def perspective_needRulesets(self, rulesetHashes):
+ """perspective_* methods are to be called remotely"""
+ return self.server.needRulesets(rulesetHashes)
+
+ def perspective_joinTable(self, tableid):
+ """perspective_* methods are to be called remotely"""
+ return self.server.joinTable(self, tableid)
+
+ def perspective_leaveTable(self, tableid):
+ """perspective_* methods are to be called remotely"""
+ return self.server.leaveTable(self, tableid)
+
+ def perspective_newTable(
+ self, ruleset, playOpen, autoPlay, wantedGame, tableId=None):
+ """perspective_* methods are to be called remotely"""
+ wantedGame = nativeString(wantedGame)
+ return self.server.newTable(self, ruleset, playOpen, autoPlay, wantedGame, tableId)
+
+ def perspective_startGame(self, tableid):
+ """perspective_* methods are to be called remotely"""
+ return self.server.startGame(self, tableid)
+
+ def perspective_logout(self):
+ """perspective_* methods are to be called remotely"""
+ self.detached(None)
+
+ def perspective_chat(self, chatString):
+ """perspective_* methods are to be called remotely"""
+ return self.server.chat(chatString)
+
+ def __unicode__(self):
+ return self.name
+
+
diff --git a/src/util.py b/src/util.py
index a3a6e5c..e3e9971 100644
--- a/src/util.py
+++ b/src/util.py
@@ -27,19 +27,19 @@ import os
import datetime
import time
import subprocess
+import gc
from locale import getpreferredencoding
from sys import stdout
+
+from common import Debug, isPython3, unicode
+
try:
STDOUTENCODING = stdout.encoding
except AttributeError:
STDOUTENCODING = None
if not STDOUTENCODING:
- STDOUTENCODING = getpreferredencoding()
-
-# util must not depend on kde
-
-from common import Debug, isPython3, unicode
+ STDOUTENCODING = getpreferredencoding() # pylint: disable=redefined-variable-type
def stack(msg, limit=6):
@@ -69,8 +69,8 @@ def callers(count=5, exclude=None):
excluding.extend(['_dataReceived', 'dataReceived', 'gotItem'])
excluding.extend(['callWithContext', '_doReadOrWrite', 'doRead'])
excluding.extend(['callers', 'debug'])
- names = list(([x[2] for x in stck if x[2] not in excluding]))
- names = reversed(names[-count:])
+ _ = list(([x[2] for x in stck if x[2] not in excluding]))
+ names = reversed(_[-count:])
result = '.'.join(names)
return '[{}]'.format(result)
@@ -105,8 +105,6 @@ def uniqueList(seq):
seen_add = seen.add
return [x for x in seq if x not in seen and not seen_add(x)]
-import gc
-
def _getr(slist, olist, seen):
"""Recursively expand slist's objects into olist, using seen to track
@@ -156,7 +154,6 @@ def kprint(*args, **kwargs):
arg = repr(arg)
arg = arg.encode(STDOUTENCODING, 'ignore')
newArgs.append(arg)
- # we need * magic: pylint: disable=star-args
try:
print(*newArgs, sep=kwargs.get('sep', ' '),
end=kwargs.get('end', '\n'), file=kwargs.get('file'))
@@ -213,7 +210,7 @@ def checkMemory():
print('}}} done')
# code like this may help to find specific things
- if True:
+ if True: # pylint: disable=using-constant-test
interesting = ('Client', 'Player', 'Game')
for obj in gc.garbage:
if hasattr(obj, 'cell_contents'):
diff --git a/src/wall.py b/src/wall.py
index 483e96c..fc32eb8 100644
--- a/src/wall.py
+++ b/src/wall.py
@@ -108,7 +108,7 @@ class Wall(object):
dealTiles = self.living[:count]
self.living = self.living[count:]
return list(self.__nameTile(*x)
- for x in zip(dealTiles, tiles)) # pylint: disable=W0142
+ for x in zip(dealTiles, tiles))
def build(self, shuffleFirst=False):
"""virtual: build visible wall"""
diff --git a/src/winprep.py b/src/winprep.py
index e836218..f9f6bd2 100644
--- a/src/winprep.py
+++ b/src/winprep.py
@@ -23,6 +23,8 @@ is where this program resides. Below you find a code
block that might have to be adapted.
"""
+from __future__ import print_function
+
from subprocess import check_output, call
from shutil import copy, move, copytree, rmtree
@@ -44,7 +46,6 @@ def makeIcon(svgName, icoName):
resolutions = (16, 24, 32, 40, 48, 64, 96, 128)
try:
for resolution in resolutions:
- pngFile = '{}{}.png'.format(icoName, resolution)
call(
'inkscape -z -e {outfile} -w {resolution} '
'-h {resolution} {infile}'.format(
@@ -55,15 +56,16 @@ def makeIcon(svgName, icoName):
pngFiles=' '.join(pngName(x)
for x in resolutions), icoName=icoName).split())
finally:
- for resolution in resolutions:
- if os.path.exists(pngName(resolution)):
- os.remove(pngName(resolution))
+ for resolution2 in resolutions:
+ # if we re-use resolution, pylint sees a problem ...
+ if os.path.exists(pngName(resolution2)):
+ os.remove(pngName(resolution2))
os.rmdir(tmpDir)
dataDir = check_output(
- "kde4-config --expandvars --install data".format(type).split()).strip()
+ "kde4-config --expandvars --install data".split()).strip()
iconDir = check_output(
- "kde4-config --expandvars --install icon".format(type).split()).strip()
+ "kde4-config --expandvars --install icon".split()).strip()
oxy48 = iconDir + '/oxygen/48x48'
oxy48Cat = oxy48 + '/categories/'
oxy48Act = oxy48 + '/actions/'
@@ -132,21 +134,20 @@ languages = (
# languages = ('de', 'zh_TW')
for lang in languages:
- print 'getting language', lang
+ print('getting language', lang)
os.makedirs(DEST + '/locale/{}/LC_MESSAGES'.format(lang))
for directory, filename in (
- ('kdegames', 'kajongg'), ('kdegames', 'libkmahjongg'),
+ ('kdegames', 'kajongg'), ('kdegames', 'libkmahjongg'),
('kdelibs', 'kdelibs4'), ('qt', 'kdeqt')):
mo_data = check_output(
'svn cat svn://anonsvn.kde.org/home/kde/'
'trunk/l10n-kde4/{}/messages/{}/{}.po'.format(
lang, directory, filename).split())
- with open('x.po'.format(filename), 'wb') as outfile:
+ with open('x.po', 'wb') as outfile:
outfile.write(mo_data)
call(
'msgfmt x.po -o {}/locale/{}/LC_MESSAGES/{}.mo'.format(
DEST,
lang,
- filename).split(
- ))
+ filename).split())
os.remove('x.po')