summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWolfgang Rohdewald <wolfgang@rohdewald.de>2016-08-24 05:58:55 (GMT)
committerWolfgang Rohdewald <wolfgang@rohdewald.de>2016-08-25 06:43:10 (GMT)
commit0ab35cc2db81888d65deb5be918bb1c09bef9855 (patch)
treea09a996674d3eaa96914781ee95d56ab742775a6
parented9bf140b747b56bfb3c7347d5d796b7a7d27539 (diff)
new Classes Wind, East etc.
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/board.py21
-rw-r--r--src/common.py1
-rw-r--r--src/game.py47
-rw-r--r--src/hand.py7
-rwxr-xr-xsrc/kajonggtest.py1
-rw-r--r--src/message.py11
-rw-r--r--src/move.py13
-rw-r--r--src/player.py7
-rw-r--r--src/rulecode.py32
-rw-r--r--src/scene.py5
-rw-r--r--src/scoring.py10
-rw-r--r--src/scoringdialog.py9
-rwxr-xr-xsrc/scoringtest.py65
-rw-r--r--src/servertable.py9
-rw-r--r--src/tile.py47
-rw-r--r--src/tileset.py3
-rw-r--r--src/tilesetselector.py5
-rw-r--r--src/uiwall.py3
-rw-r--r--src/wind.py143
20 files changed, 314 insertions, 126 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fe004aa..7025199 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,7 @@ src/client.py
src/intelligence.py
src/altint.py
src/common.py
+src/wind.py
src/compat.py
src/rand.py
src/config.py
diff --git a/src/board.py b/src/board.py
index ba7fa36..0af0759 100644
--- a/src/board.py
+++ b/src/board.py
@@ -35,7 +35,8 @@ from message import Message
from util import kprint, stack, uniqueList
from log import logDebug, logException, m18n, m18nc
-from common import WINDS, LIGHTSOURCES, Internal, Debug, isAlive, unicode
+from common import LIGHTSOURCES, Internal, Debug, isAlive, unicode
+from wind import Wind, East
ROUNDWINDCOLOR = QColor(235, 235, 173)
@@ -57,8 +58,9 @@ class PlayerWind(QGraphicsEllipseItem):
def __init__(self, name, tileset, roundsFinished=0, parent=None):
"""generate new wind tile"""
+ assert isinstance(name, Wind), 'PlayerWind expects Wind, not {}'.format(type(name))
if not len(WINDPIXMAPS):
- WINDPIXMAPS[('E', False)] = None # avoid recursion
+ WINDPIXMAPS[(East, False)] = None # avoid recursion
self.genWINDPIXMAPS()
QGraphicsEllipseItem.__init__(self)
if parent:
@@ -75,7 +77,7 @@ class PlayerWind(QGraphicsEllipseItem):
def genWINDPIXMAPS():
"""prepare wind tiles"""
tileset = Tileset(Internal.Preferences.windTilesetName)
- for wind in WINDS:
+ for wind in Wind.all4:
for prevailing in False, True:
pwind = PlayerWind(wind, tileset, prevailing)
pMap = QPixmap(40, 40)
@@ -108,14 +110,15 @@ class PlayerWind(QGraphicsEllipseItem):
def setWind(self, name, roundsFinished):
"""change the wind"""
+ assert isinstance(name, Wind) and name.svgName, 'name {} must be a real Wind but is {}'.format(
+ name, type(name))
self.name = name
if isinstance(roundsFinished, bool):
self.prevailing = roundsFinished
else:
- self.prevailing = name == WINDS[roundsFinished % 4]
+ self.prevailing = name == Wind.all4[roundsFinished % 4]
self.setBrush(ROUNDWINDCOLOR if self.prevailing else QColor('white'))
- windtilenr = {'N': 1, 'S': 2, 'E': 3, 'W': 4}
- self.face.setElementId('WIND_%d' % windtilenr[name])
+ self.face.setElementId(name.svgName)
class WindLabel(QLabel):
@@ -138,7 +141,7 @@ class WindLabel(QLabel):
QLabel.__init__(self, parent)
self.__wind = None
if wind is None:
- wind = 'E'
+ wind = East
self.__roundsFinished = roundsFinished
self.wind = wind
@@ -158,7 +161,7 @@ class WindLabel(QLabel):
"""update pixmaps"""
PlayerWind.genWINDPIXMAPS()
self.setPixmap(WINDPIXMAPS[(self.__wind,
- self.__wind == WINDS[min(self.__roundsFinished, 3)])])
+ self.__wind == Wind.all4[min(self.__roundsFinished, 3)])])
class Board(QGraphicsRectItem):
@@ -739,7 +742,7 @@ class SelectorBoard(CourtBoard):
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)
+ column = baseColumn + order.index(uiTile.tile.char)
uiTile.dark = False
uiTile.setBoard(self, column, row)
diff --git a/src/common.py b/src/common.py
index f26218e..b164421 100644
--- a/src/common.py
+++ b/src/common.py
@@ -57,7 +57,6 @@ else:
interpreterName = 'python2'
xrange = xrange
-WINDS = u'ESWN'
LIGHTSOURCES = [u'NE', u'NW', u'SW', u'SE']
ENGLISHDICT = {}
diff --git a/src/game.py b/src/game.py
index 5b4ec12..58659b4 100644
--- a/src/game.py
+++ b/src/game.py
@@ -30,8 +30,9 @@ from twisted.internet.defer import succeed
from util import gitHead
from rand import CountingRandom
from log import logError, logWarning, logException, logDebug, m18n
-from common import WINDS, Internal, IntDict, Debug, Options, unicodeString, unicode
+from common import Internal, IntDict, Debug, Options, unicodeString, unicode
from common import isPython3
+from wind import Wind, East
from query import Query
from rule import Ruleset
from tile import Tile, elements
@@ -95,16 +96,16 @@ class HandId(object):
self.roundsFinished = 100
return
handId = parts[min(stringIdx, len(parts) - 1)]
- if handId[0] not in WINDS:
- logException('--game=%s with / must specify the round wind'
- % string)
+ if handId[0].lower() not in 'eswn':
+ logException('--game=%s must specify the round wind' % string)
+ handWind = Wind(handId[0])
ruleset = self.game.ruleset
- self.roundsFinished = WINDS.index(handId[0])
+ self.roundsFinished = handWind.__index__()
if self.roundsFinished > ruleset.minRounds:
logWarning(
u'Ruleset %s has %d minimum rounds but you want round %d(%s)'
% (ruleset.name, ruleset.minRounds, self.roundsFinished + 1,
- handId[0]))
+ handWind))
self.roundsFinished = ruleset.minRounds
return
self.rotated = int(handId[1]) - 1
@@ -154,7 +155,7 @@ class HandId(object):
num = (num - 1) // 26
if not charId:
charId = ' ' # align to the most common case
- wind = (WINDS + 'X')[self.roundsFinished]
+ wind = Wind.all4[self.roundsFinished % 4]
if withSeed:
seed = str(self.seed)
else:
@@ -208,6 +209,9 @@ class Game(object):
"""
# pylint: disable=too-many-statements
assert self.__class__ != Game, 'Do not directly instantiate Game'
+ for wind, name in names:
+ assert isinstance(wind, Wind), 'Game.__init__ expects Wind objects'
+ assert isinstance(name, (str, unicode)), 'Game.__init__: name must be string and not {}'.format(type(name))
self.players = Players()
# if we fail later on in init, at least we can still close the program
self.myself = None
@@ -329,7 +333,7 @@ class Game(object):
@property
def roundWind(self):
"""the round wind for Hand"""
- return 'eswn'[self.roundsFinished % 4]
+ return Wind.all[self.roundsFinished % 4]
@winner.setter
def winner(self, value):
@@ -416,17 +420,16 @@ class Game(object):
"""assign random seats to the players and assign winds"""
self.players.sort(key=lambda x: x.name)
self.randomGenerator.shuffle(self.players)
- for player, wind in zip(self.players, WINDS):
+ for player, wind in zip(self.players, Wind.all4):
player.wind = wind
def __exchangeSeats(self):
"""execute seat exchanges according to the rules"""
- winds = self.shiftRules.split(',')[(self.roundsFinished - 1) % 4]
- players = list(self.players[x] for x in winds)
+ winds = list(x for x in self.shiftRules.split(',')[(self.roundsFinished - 1) % 4])
+ players = list(self.players[Wind(x)] for x in winds)
pairs = list(players[x:x + 2] for x in range(0, len(winds), 2))
for playerA, playerB in self._mustExchangeSeats(pairs):
playerA.wind, playerB.wind = playerB.wind, playerA.wind
- self.sortPlayers()
def _mustExchangeSeats(self, pairs):
"""filter: which player pairs should really swap places?"""
@@ -435,10 +438,8 @@ 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'] # pylint: disable=invalid-sequence-index
- # TODO: new class Wind(str) with len==1 and __index__ 0..3 for ESWN
- # that will make pylint happier
+ self.players.sort(key=lambda x: x.wind)
+ self.activePlayer = self.players[East]
if Internal.scene:
if self.belongsToHumanPlayer():
while self.players[0] != self.myself:
@@ -542,7 +543,7 @@ class Game(object):
"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,
+ self.roundWind.char, player.wind,
player.handTotal, player.payment, player.balance,
self.rotated, self.notRotated),
(player.hand.string, manualrules))
@@ -658,8 +659,8 @@ class Game(object):
if not qScoreRecords:
# this should normally not happen
qScoreRecords = list([
- tuple([qGameRecord[x], WINDS[x], 0, False, 'E'])
- for x in range(4)])
+ tuple([qGameRecord[wind], wind.char, 0, False, East.char])
+ for wind in Wind.all4])
if len(qScoreRecords) != 4:
logError(u'game %d inconsistent: There should be exactly '
'4 score records for the last hand' % gameid)
@@ -673,7 +674,7 @@ class Game(object):
logError(u'game %d inconsistent: All score records for the same '
'hand must have the same prevailing wind' % gameid)
- players = list(tuple([x[1], Game.__getName(x[0])])
+ players = list(tuple([Wind(x[1]), Game.__getName(x[0])])
for x in qScoreRecords)
# create the game instance.
@@ -692,7 +693,7 @@ class Game(object):
player.getsPayment(record[2])
if record[3]:
game.winner = player
- game.roundsFinished = WINDS.index(qScoreRecords[0][4]) # prevailing wind
+ game.roundsFinished = Wind(qScoreRecords[0][4]).__index__()
game.handctr += 1
game.notRotated += 1
game.maybeRotateWinds()
@@ -729,7 +730,7 @@ class Game(object):
(self.handId, winner, guilty))
guilty.hand.usedRules.append((payAction, None))
score = winner.handTotal
- score = score * 6 if winner.wind == 'E' else score * 4
+ score = score * 6 if winner.wind == East else score * 4
guilty.getsPayment(-score)
winner.getsPayment(score)
return
@@ -742,7 +743,7 @@ class Game(object):
self.debug(' %s' % (line))
for player2 in self.players:
if id(player1) != id(player2):
- if player1.wind == 'E' or player2.wind == 'E':
+ if player1.wind == East or player2.wind == East:
efactor = 2
else:
efactor = 1
diff --git a/src/hand.py b/src/hand.py
index d04e237..c509ff9 100644
--- a/src/hand.py
+++ b/src/hand.py
@@ -61,7 +61,10 @@ class Hand(object):
mjRule is the one out of mjRules with the highest resulting score. Every
hand gets an mjRule even it is not a wining hand, it is the one which
- was used for rearranging the hiden tiles to melds."""
+ was used for rearranging the hiden tiles to melds.
+
+ suits include dragons and winds."""
+
# pylint: disable=too-many-instance-attributes
indent = 0
@@ -209,7 +212,7 @@ class Hand(object):
@property
def ownWind(self):
"""for easier usage"""
- return self.player.wind.lower()
+ return self.player.wind
@property
def roundWind(self):
diff --git a/src/kajonggtest.py b/src/kajonggtest.py
index c069536..b456568 100755
--- a/src/kajonggtest.py
+++ b/src/kajonggtest.py
@@ -271,6 +271,7 @@ class Job(StrMixin):
def start(self):
"""start this job"""
+ # pylint: disable=too-many-branches
self.server = Server(self)
# never login to the same server twice at the
# same time with the same player name
diff --git a/src/message.py b/src/message.py
index 9be3d5c..5ab1999 100644
--- a/src/message.py
+++ b/src/message.py
@@ -26,6 +26,7 @@ from tile import Tile, TileList
from meld import Meld, MeldList
from common import Internal, Debug, Options, long
from common import unicode, unicodeString
+from wind import Wind
from dialogs import Sorry
# pylint: disable=super-init-not-called
@@ -73,6 +74,8 @@ class Message(object):
cls = value.__class__
if cls in (Tile, TileList, Meld, MeldList):
return str(value).encode()
+ elif isinstance(value, Wind):
+ return str(value).encode()
elif isinstance(value, Message):
return value.name
elif isinstance(value, (list, tuple)):
@@ -433,7 +436,7 @@ class MessageProposeGameId(ServerMessage):
def clientAction(self, client, move):
"""ask the client"""
- # move.source are the players in seating order
+ # move.playerNames are the players in seating order
# we cannot just use table.playerNames - the seating order is now
# different (random)
return client.reserveGameId(move.gameid)
@@ -470,12 +473,12 @@ class MessageReadyForGameStart(ServerMessage):
client.name)
client.tableList.hide()
return result
- # move.source are the players in seating order
+ # move.playerNames 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)
+ move.wantedGame, move.playerNames, shouldSave=move.shouldSave).addCallback(hideTableList)
class MessageNoGameStart(NotifyAtOnceMessage):
@@ -503,7 +506,7 @@ class MessageReadyForHandStart(ServerMessage):
def clientAction(self, client, move):
"""ask the client"""
- return client.readyForHandStart(move.source, move.rotateWinds)
+ return client.readyForHandStart(move.playerNames, move.rotateWinds)
class MessageInitHand(ServerMessage):
diff --git a/src/move.py b/src/move.py
index 2d7e663..c1d5c3f 100644
--- a/src/move.py
+++ b/src/move.py
@@ -22,6 +22,7 @@ import weakref
from common import Debug, unicodeString, StrMixin, nativeString
from message import Message
+from wind import Wind
from tile import Tile, TileList
from meld import Meld, MeldList
@@ -57,9 +58,21 @@ class Move(StrMixin):
self.__setattr__(key, MeldList(nativeString(value)))
elif key in ('wantedGame', 'score'):
self.__setattr__(key, nativeString(value))
+ elif key == 'playerNames':
+ self.__setattr__(key, self.convertWinds(value))
else:
self.__setattr__(key, value)
+ @staticmethod
+ def convertWinds(tuples):
+ """convert wind strings to Wind objects"""
+ if isinstance(tuples[0][0], Wind):
+ return tuples
+ result = list()
+ for wind, name in tuples:
+ result.append(tuple([Wind(wind), name]))
+ return result
+
@property
def player(self):
"""hide weakref"""
diff --git a/src/player.py b/src/player.py
index 988fb76..e5b9980 100644
--- a/src/player.py
+++ b/src/player.py
@@ -22,8 +22,9 @@ import weakref
from collections import defaultdict
from log import logException, logWarning, m18n, m18nc, m18nE
-from common import WINDS, IntDict, Debug, unicode
+from common import IntDict, Debug, unicode
from common import StrMixin
+from wind import East
from query import Query
from tile import Tile, TileList, elements
from meld import Meld, MeldList
@@ -133,7 +134,7 @@ class Player(StrMixin):
self.__name = ''
Players.createIfUnknown(name)
self.name = name
- self.wind = WINDS[0]
+ self.wind = East
self.intelligence = AIDefault(self)
self.visibleTiles = IntDict(game.visibleTiles) if game else IntDict()
self.handCache = {}
@@ -174,7 +175,7 @@ class Player(StrMixin):
"""write once"""
assert self.__name == ''
assert value
- assert isinstance(value, unicode)
+ assert isinstance(value, unicode), 'Player.name must be unicode but not {}'.format(type(value))
self.__name = value
@property
diff --git a/src/rulecode.py b/src/rulecode.py
index d4ded2b..24c360e 100644
--- a/src/rulecode.py
+++ b/src/rulecode.py
@@ -20,7 +20,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from tile import Tile, elements
from meld import Meld, MeldList
-from common import IntDict, WINDS
+from common import IntDict
+from wind import East
from message import Message
from query import Query
from permutations import Permutations
@@ -138,7 +139,7 @@ class ConcealedHonorsKong(RuleCode):
class OwnWindPungKong(RuleCode):
def appliesToMeld(hand, meld):
- return meld[0].value == hand.ownWind
+ return meld[0].value is hand.ownWind
def mayApplyToMeld(meld):
"""for meld rules which depend on context like hand.ownWind, we want
@@ -152,7 +153,7 @@ class OwnWindPungKong(RuleCode):
class OwnWindPair(RuleCode):
def appliesToMeld(hand, meld):
- return meld[0].value == hand.ownWind
+ return meld[0].value is hand.ownWind
def mayApplyToMeld(meld):
return meld.isPair and meld.isWindMeld
@@ -161,7 +162,7 @@ class OwnWindPair(RuleCode):
class RoundWindPungKong(RuleCode):
def appliesToMeld(hand, meld):
- return meld[0].value == hand.roundWind
+ return meld[0].value is hand.roundWind
def mayApplyToMeld(meld):
return meld.isPungKong and meld.isWindMeld
@@ -170,7 +171,7 @@ class RoundWindPungKong(RuleCode):
class RoundWindPair(RuleCode):
def appliesToMeld(hand, meld):
- return meld[0].value == hand.roundWind
+ return meld[0].value is hand.roundWind
def mayApplyToMeld(meld):
return meld.isPair and meld.isWindMeld
@@ -982,7 +983,7 @@ class ScratchingPole(RuleCode):
class StandardRotation(RuleCode):
def rotate(game):
- return game.winner and game.winner.wind != 'E'
+ return game.winner and game.winner.wind is not East
class EastWonNineTimesInARow(RuleCode):
@@ -997,12 +998,11 @@ class EastWonNineTimesInARow(RuleCode):
if game.isScoringGame():
# we are only proposing for the last needed Win
needWins -= 1
- if game.winner and game.winner.wind == 'E' and game.notRotated >= needWins:
- prevailing = WINDS[game.roundsFinished % 4]
+ if game.winner and game.winner.wind is East and game.notRotated >= needWins:
eastMJCount = int(Query("select count(1) from score "
"where game=%d and won=1 and wind='E' and player=%d "
"and prevailing='%s'" %
- (game.gameid, game.players['E'].nameid, prevailing)).records[0][0])
+ (game.gameid, game.players[East].nameid, game.roundWind.char)).records[0][0])
return eastMJCount == needWins
return False
@@ -1181,7 +1181,7 @@ class ThirteenOrphans(MJRule):
class OwnFlower(RuleCode):
def appliesToMeld(hand, meld):
- return meld[0].value == hand.ownWind
+ return meld[0].value is hand.ownWind
def mayApplyToMeld(meld):
# pylint: disable=unsubscriptable-object
@@ -1192,7 +1192,7 @@ class OwnFlower(RuleCode):
class OwnSeason(RuleCode):
def appliesToMeld(hand, meld):
- return meld[0].value == hand.ownWind
+ return meld[0].value is hand.ownWind
def mayApplyToMeld(meld):
# pylint: disable=unsubscriptable-object
@@ -1203,7 +1203,7 @@ class OwnSeason(RuleCode):
class OwnFlowerOwnSeason(RuleCode):
def appliesToHand(hand):
- return sum(x.isBonus and x[0].value == hand.ownWind for x in hand.bonusMelds) == 2
+ return sum(x.isBonus and x[0].value is hand.ownWind for x in hand.bonusMelds) == 2
class AllFlowers(RuleCode):
@@ -1265,11 +1265,11 @@ class TwofoldFortune(RuleCode):
class BlessingOfHeaven(RuleCode):
def appliesToHand(hand):
- return hand.ownWind == Tile.east and hand.lastSource == '1'
+ return hand.ownWind is East and hand.lastSource == '1'
def selectable(hand):
"""for scoring game"""
- return (hand.ownWind == Tile.east
+ return (hand.ownWind is East
and hand.lastSource and hand.lastSource in 'wd'
and not hand.announcements - {'a'})
@@ -1277,11 +1277,11 @@ class BlessingOfHeaven(RuleCode):
class BlessingOfEarth(RuleCode):
def appliesToHand(hand):
- return hand.ownWind != Tile.east and hand.lastSource == '1'
+ return hand.ownWind is not East and hand.lastSource == '1'
def selectable(hand):
"""for scoring game"""
- return (hand.ownWind != Tile.east
+ return (hand.ownWind is not East
and hand.lastSource and hand.lastSource in 'wd'
and not hand.announcements - {'a'})
diff --git a/src/scene.py b/src/scene.py
index b4ccc43..24fac69 100644
--- a/src/scene.py
+++ b/src/scene.py
@@ -21,8 +21,9 @@ 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 LIGHTSOURCES, Internal, isAlive, ZValues, Debug
from common import nativeString
+from wind import Wind
from twisted.internet.defer import succeed
from qt import Qt, QMetaObject, variantValue
@@ -521,7 +522,7 @@ class ScoringScene(GameScene):
mod = event.modifiers()
key = event.key()
wind = chr(key % 128)
- windsX = WINDS + u'X'
+ windsX = ''.join(x.char for x in Wind.all)
moveCommands = m18nc('kajongg:keyboard commands for moving tiles to the players '
'with wind ESWN or to the central tile selector (X)', windsX)
uiTile = self.focusItem()
diff --git a/src/scoring.py b/src/scoring.py
index c81cf9d..7c25088 100644
--- a/src/scoring.py
+++ b/src/scoring.py
@@ -25,7 +25,8 @@ from qt import QGraphicsRectItem, QGraphicsSimpleTextItem
from qt import QPushButton, QMessageBox, QComboBox
-from common import Internal, isAlive, WINDS, unicode
+from common import Internal, isAlive, unicode
+from wind import Wind
from animation import animate
from log import logError, logDebug, logWarning, m18n
from query import Query
@@ -68,7 +69,7 @@ class SelectPlayers(SelectRuleset):
decorateWindow(self, m18n('Select four players'))
self.names = None
self.nameWidgets = []
- for idx, wind in enumerate(WINDS):
+ for idx, wind in enumerate(Wind.all4):
cbName = QComboBox()
cbName.manualSelect = False
# increase width, we want to see the full window title
@@ -583,12 +584,11 @@ class ScoringGame(Game):
"VALUES(%d,1,%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, 0,
+ self.roundWind, player.wind, 0,
amount, player.balance, self.rotated, self.notRotated),
(player.hand.string, offense.name))
Internal.mainWindow.updateGUI()
-
def scoreGame():
"""show all games, select an existing game or create a new game"""
Players.load()
@@ -608,4 +608,4 @@ def scoreGame():
selectDialog = SelectPlayers()
if not selectDialog.exec_():
return
- return ScoringGame(list(zip(WINDS, selectDialog.names)), selectDialog.cbRuleset.current)
+ return ScoringGame(list(zip(Wind.all4, selectDialog.names)), selectDialog.cbRuleset.current)
diff --git a/src/scoringdialog.py b/src/scoringdialog.py
index 19ab08b..ad54b10 100644
--- a/src/scoringdialog.py
+++ b/src/scoringdialog.py
@@ -44,11 +44,12 @@ from modeltest import ModelTest
from rulesetselector import RuleTreeView
from board import WindLabel, WINDPIXMAPS
from log import m18n, m18nc
-from common import WINDS, Internal, Debug, unicode
+from common import Internal, Debug, unicode
from statesaver import StateSaver
from query import Query
from guiutil import ListComboBox, Painter, decorateWindow, BlockSignals
from tree import TreeItem, RootItem, TreeModel
+from wind import Wind
class ScoreTreeItem(TreeItem):
@@ -320,8 +321,8 @@ class HandResult(object):
self.notRotated = notRotated
self.penalty = bool(penalty)
self.won = won
- self.prevailing = prevailing
- self.wind = wind
+ self.prevailing = Wind(prevailing)
+ self.wind = Wind(wind)
self.points = points
self.payments = payments
self.balance = balance
@@ -1052,7 +1053,7 @@ class ScoringDialog(QWidget):
else:
for idx, player in enumerate(self.game.players):
self.windLabels[idx].setPixmap(WINDPIXMAPS[(player.wind,
- player.wind == WINDS[self.game.roundsFinished % 4])])
+ player.wind == self.game.roundWind)])
self.computeScores()
self.spValues[0].setFocus()
self.spValues[0].selectAll()
diff --git a/src/scoringtest.py b/src/scoringtest.py
index 8cae4b6..705e495 100755
--- a/src/scoringtest.py
+++ b/src/scoringtest.py
@@ -23,7 +23,8 @@ from __future__ import print_function
import unittest
-from common import Debug, isPython3, WINDS # pylint: disable=unused-import
+from common import Debug, isPython3, unicode # pylint: disable=unused-import
+from wind import Wind, East, South, West, North
from player import Players
from game import PlayingGame
from hand import Hand, Score
@@ -45,7 +46,7 @@ for _ in RULESETS[2:]:
Players.createIfUnknown = str
# RULESETS=RULESETS[:1]
-GAMES = list([PlayingGame(list(tuple([wind, wind]) for wind in WINDS), x)
+GAMES = list([PlayingGame(list(tuple([wind, unicode(wind.char)]) for wind in Wind.all4), x)
for x in RULESETS])
PROGRAM = None
@@ -84,12 +85,14 @@ class NoWin(Expected):
class Helpers(object):
"""for my test classes"""
- # pylint: disable=no-member
+ # pylint: disable=no-member, too-many-locals
- def scoreTest(self, string, expected, winds=None, totals=None):
+ def scoreTest(self, string, expected, myWind=None, roundWind=None, totals=None):
"""execute one scoreTest test"""
- if winds is None:
- winds = 'ee'
+ if myWind is None:
+ myWind = East
+ if roundWind is None:
+ roundWind = East
for idx, ruleset in enumerate(RULESETS):
if isinstance(expected, list):
expIdx = idx
@@ -103,11 +106,11 @@ class Helpers(object):
exp.ruleset = ruleset
variants = []
game = GAMES[idx]
- for widx, wind in enumerate(WINDS):
+ for widx, wind in enumerate(Wind.all4):
game.players[widx].wind = wind
- game.winner = game.players[winds[0].upper()]
+ game.winner = game.players[myWind]
game.myself = game.winner
- game.roundsFinished = 'eswn'.index(winds[1])
+ game.roundsFinished = roundWind.__index__()
game.winner.clearCache()
if Debug.hand:
print('')
@@ -200,7 +203,7 @@ class Partials(Base):
self.scoreTest('drdrdr fe Ldrdrdrdr', [NoWin(8, 1), NoWin(8, 2)])
self.scoreTest('fe', [NoWin(4), NoWin(4, 1)])
self.scoreTest('fs fw fe fn', [NoWin(16, 1), NoWin(16, 3)])
- self.scoreTest('fs ys', [NoWin(8, 1), NoWin(8, 2)], winds='se')
+ self.scoreTest('fs ys', [NoWin(8, 1), NoWin(8, 2)], myWind=South)
self.scoreTest('drdrdr Ldrdrdrdr', NoWin(4, 1))
@@ -210,9 +213,9 @@ class ZeroHand(Base):
def testMe(self):
self.scoreTest('c1c2c3 c7c8c9 b2b3b4 c5c5 s1s2s3 fw yw Lc1c1c2c3',
- [Win(points=28, doubles=2), NoWin()], winds='wn')
+ [Win(points=28, doubles=2), NoWin()], myWind=West, roundWind=North)
self.scoreTest('c1c2c3 c7c8c9 b2b3b4 drdr s1s2s3 fw yw Lc1c1c2c3',
- [Win(points=30, doubles=1), NoWin()], winds='wn')
+ [Win(points=30, doubles=1), NoWin()], myWind=West, roundWind=North)
class FalseColorGame(Base):
@@ -223,18 +226,18 @@ 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)], myWind=West, roundWind=North)
self.scoreTest(
's1s1s1 wewewe c2c3c4 c5c5 c6c6c6 Lc5c5c5',
[Win(34),
Win(30)],
- winds='wn')
+ myWind=West, roundWind=North)
self.scoreTest(
'RB1B1B1B1B2B3B4B5B6B7B8B9DrDr fe ys LDrDrDr', [Win(46, 2), NoWin()])
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)], myWind=West)
class WrigglingSnake(Base):
@@ -310,7 +313,7 @@ class Purity(Base):
'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)], myWind=West)
class TrueColorGame(Base):
@@ -337,11 +340,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)], myWind=West)
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)], myWind=West)
class LimitHands(Base):
@@ -489,7 +492,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)], myWind=North)
class BuriedTreasure(Base):
@@ -626,13 +629,13 @@ class Rest(Base):
def testMe(self):
self.scoreTest('s1s1s1s1 s2s2s2 wewe RS3S3S3 s4s4s4 Ls2s2s2s2',
- [Win(44, 2), Win(44, 1)], winds='sw')
+ [Win(44, 2), Win(44, 1)], myWind=South, roundWind=West)
self.scoreTest('s1s1s1s1 s2s2s2 RWeWeS3S3S3 s4s4s4 me LS3S3S3S3',
- [Win(46, 3), Win(46, 1)], winds='sw')
+ [Win(46, 3), Win(46, 1)], myWind=South, roundWind=West)
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)], myWind=North)
self.scoreTest(
'b3b3b3b3 RDbDbDb drdrdr weWeWewe s2s2 Ls2s2s2', [Win(76, 5), Win(72, 5)])
self.scoreTest('s2s2s2 s2s3s4 RB1B1B1B1 c9C9C9c9 Ls2s2s3s4', NoWin(42))
@@ -661,9 +664,9 @@ class Rest(Base):
self.scoreTest('RB1B1B1B1B2B3B4B5B6B8B8B2B2 fe fs fn fw LB4',
[NoWin(28, 1), NoWin(28, 3)])
self.scoreTest('wewe wswsws RWnWnWn wwwwwwww b1b1b1 mz Lb1b1b1b1',
- [Win(54, 6), Win(54, 4)], winds='ne')
+ [Win(54, 6), Win(54, 4)], myWind=North)
self.scoreTest('wswsws RWeWeWnWnWnB1B1B1 wwwwwwww mz LB1B1B1B1',
- [Win(60, 6), Win(60, 4)], winds='ne')
+ [Win(60, 6), Win(60, 4)], myWind=North)
self.scoreTest(
'RB2B2 b4b4b4 b5b6b7 b7b8b9 c1c1c1 md Lb7b7b8b9', [Win(28), NoWin()])
self.scoreTest(
@@ -695,9 +698,9 @@ class OriginalCall(Base):
def testMe(self):
# in DMJL, b4 would also win:
self.scoreTest('s1s1s1 s1s2s3 RB6B6B6B8B8B8B5B5 fn yn m.a LB5',
- [Win(44, 2), Win(42, 3)], winds='ne')
+ [Win(44, 2), Win(42, 3)], myWind=North)
self.scoreTest('s1s1s1 s1s2s3 RB6B6B6B8B8B8B5 fn yn m.a LB5',
- [NoWin(20, 1), NoWin(20, 2)], winds='ne')
+ [NoWin(20, 1), NoWin(20, 2)], myWind=North)
class RobbingKong(Base):
@@ -708,13 +711,13 @@ class RobbingKong(Base):
# this hand is only possible if the player declared a hidden chow.
# is that legal?
self.scoreTest('s1s2s3 s1s2s3 RB6B6B7B7B8B8B5 fn yn m.a LB5',
- [NoWin(8, 1), NoWin(8, 2)], winds='ne')
+ [NoWin(8, 1), NoWin(8, 2)], myWind=North)
self.scoreTest('s1s2s3 s2s3s4 RB6B6B7B7B8B8B5B5 fn yn mka Ls1s1s2s3',
- [Win(28, 4), NoWin()], winds='ne')
+ [Win(28, 4), NoWin()], myWind=North)
self.scoreTest('s4s5s6 RS1S2S3B6B6B7B7B8B8B5B5 fn yn m.a LS1S1S2S3',
- [Win(30, 3), NoWin()], winds='ne')
+ [Win(30, 3), NoWin()], myWind=North)
self.scoreTest('s4s5s6 RS1S2S3B6B6B7B7B8B8 fn yn m.a Ls4s4s5s6',
- [NoWin(8, 1), NoWin(8, 2)], winds='ne')
+ [NoWin(8, 1), NoWin(8, 2)], myWind=North)
class Blessing(Base):
@@ -901,7 +904,7 @@ class AllPairHonors(Base):
'RWeWeS1S1B9B9DgDgDrDrWsWwWw LS1',
[NoWin(6),
NoWin(limits=0.2)],
- winds='wn')
+ myWind=West, roundWind=North)
self.scoreTest('RWeWeS2S2B9B9DgDgDrDrWsWsWwWw LS2S2S2', NoWin())
self.scoreTest('RDbDbDgDgDrDrWsWsWnWnS9C1C1C9 LDrDrDr', NoWin())
diff --git a/src/servertable.py b/src/servertable.py
index a27eaef..fd6368b 100644
--- a/src/servertable.py
+++ b/src/servertable.py
@@ -32,7 +32,8 @@ from itertools import chain
from twisted.spread import pb
-from common import Debug, WINDS, Internal
+from common import Debug, Internal
+from wind import Wind
from util import Duration
from message import Message, ChatMessage
from log import logDebug, logError, m18nE, m18n, m18ncE
@@ -233,7 +234,7 @@ class ServerTable(Table):
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])
+ names = list(tuple([Wind.all4[idx], name])
for idx, name in enumerate(names))
self.client = Client()
# Game has a weakref to client, so we must keep
@@ -336,7 +337,7 @@ class ServerTable(Table):
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))
+ wantedGame=game.wantedGame, playerNames=list((x.wind, x.name) for x in game.players))
block.callback(self.startGame)
def startGame(self, requests):
@@ -664,7 +665,7 @@ class ServerTable(Table):
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)
+ playerNames=playerNames, rotateWinds=rotateWinds, token=token)
def abort(self, message, *args):
"""abort the table. Reason: message/args"""
diff --git a/src/tile.py b/src/tile.py
index 5ded252..42e9d95 100644
--- a/src/tile.py
+++ b/src/tile.py
@@ -22,7 +22,7 @@ from __future__ import print_function
from log import m18n, m18nc, logException
from common import IntDict
-
+from wind import Wind, East, South, West, North
class Tile(str):
@@ -40,8 +40,11 @@ class Tile(str):
- two args: a char and either a char or an int -1..11
group is a char: b=bonus w=wind d=dragon X=unknown
- value is 1..9 for real suit tiles, -1/0/10/11 for usage in AI, and a char
- for dragons, winds,boni
+ value is
+ 1..9 for real suit tiles
+ -1/0/10/11 for usage in AI
+ Wind for winds and boni
+ bgr for dragons
"""
# pylint: disable=too-many-public-methods,too-many-instance-attributes
cache = {}
@@ -72,7 +75,6 @@ class Tile(str):
dragons = 'bgr'
white, green, red = dragons
winds = 'eswn'
- east, south, west, north = winds
numbers = range(1, 10)
terminals = list([1, 9])
minors = range(2, 9)
@@ -84,6 +86,7 @@ class Tile(str):
@classmethod
def __build(cls, *args):
"""build a new Tile object out of args"""
+ # pylint: disable=too-many-statements
if len(args) == 1:
arg0, arg1 = args[0]
else:
@@ -101,13 +104,19 @@ class Tile(str):
result.isWind = result.lowerGroup == Tile.wind
result.isHonor = result.isDragon or result.isWind
- if result.isHonor or result.isBonus:
+ result.isTerminal = False
+ result.isNumber = False
+ result.isReal = True
+
+ if result.isWind or result.isBonus:
+ result.value = Wind(result[1])
+ result.char = result[1]
+ elif result.isDragon:
result.value = result[1]
- result.isTerminal = False
- result.isNumber = False
- result.isReal = True
+ result.char = result.value
else:
result.value = ord(result[1]) - 48
+ result.char = result.value
result.isTerminal = result.value in Tile.terminals
result.isNumber = True
result.isReal = result.value in Tile.numbers
@@ -209,10 +218,10 @@ class Tile(str):
Tile.white: m18nc('kajongg', 'white'),
Tile.red: m18nc('kajongg', 'red'),
Tile.green: m18nc('kajongg', 'green'),
- Tile.east: m18nc('kajongg', 'East'),
- Tile.south: m18nc('kajongg', 'South'),
- Tile.west: m18nc('kajongg', 'West'),
- Tile.north: m18nc('kajongg', 'North')}
+ East: m18nc('kajongg', 'East'),
+ South: m18nc('kajongg', 'South'),
+ West: m18nc('kajongg', 'West'),
+ North: m18nc('kajongg', 'North')}
for idx in Tile.numbers:
names[idx] = chr(idx + 48)
return names[self.value]
@@ -221,10 +230,10 @@ class Tile(str):
"""returns name of a single tile"""
if self.group.lower() == Tile.wind:
result = {
- Tile.east: m18n('East Wind'),
- Tile.south: m18n('South Wind'),
- Tile.west: m18n('West Wind'),
- Tile.north: m18n('North Wind')}[self.value]
+ East: m18n('East Wind'),
+ South: m18n('South Wind'),
+ West: m18n('West Wind'),
+ North: m18n('North Wind')}[self.value]
else:
result = m18nc('kajongg tile name', '{group} {value}')
return result.format(value=self.valueName(), group=self.groupName())
@@ -314,8 +323,8 @@ class Elements(object):
for tile in self.minors:
self.occurrence[tile] = 4
for bonus in Tile.boni:
- for wind in Tile.winds:
- self.occurrence[Tile(bonus, wind)] = 1
+ for _ in Tile.winds:
+ self.occurrence[Tile(bonus, _)] = 1
def __filter(self, ruleset):
"""returns element names"""
@@ -333,3 +342,5 @@ class Elements(object):
Tile.unknown = Tile('Xy') # must come first
elements = Elements() # pylint: disable=invalid-name
assert not Tile.unknown.isKnown
+for wind in Wind.all4:
+ wind.tile = Tile('w', wind.char.lower())
diff --git a/src/tileset.py b/src/tileset.py
index 2bbe830..2671d2b 100644
--- a/src/tileset.py
+++ b/src/tileset.py
@@ -27,6 +27,7 @@ from qt import QString, QSizeF, QSvgRenderer
from kde import KGlobal, KStandardDirs, KConfig
from log import logWarning, logException, m18n
from common import LIGHTSOURCES, Internal
+from wind import East, South, West, North
TILESETVERSIONFORMAT = 1
@@ -147,7 +148,7 @@ class Tileset(object):
# renderer right away
self.svgName = {
- 'wn': 'WIND_1', 'ws': 'WIND_2', 'we': 'WIND_3', 'ww': 'WIND_4',
+ 'wn': North.svgName, 'ws': South.svgName, 'we': East.svgName, 'ww': West.svgName,
'db': 'DRAGON_1', 'dg': 'DRAGON_2', 'dr': 'DRAGON_3'}
for value in '123456789':
self.svgName['s%s' % value] = 'ROD_%s' % value
diff --git a/src/tilesetselector.py b/src/tilesetselector.py
index 96576f7..f5ed972 100644
--- a/src/tilesetselector.py
+++ b/src/tilesetselector.py
@@ -25,7 +25,8 @@ from tileset import Tileset
from uitile import UITile
from board import Board, FittingView
from scene import SceneWithFocusRect
-from common import Internal, WINDS
+from common import Internal
+from wind import Wind
from guiutil import loadUi
from animation import MoveImmediate
@@ -46,7 +47,7 @@ class TilesetSelector(QWidget):
self.tileView = FittingView()
self.tileView.setScene(self.tileScene)
self.tileset = Tileset(Internal.Preferences.tilesetName)
- self.uiTiles = [UITile('w' + s) for s in WINDS.lower()]
+ self.uiTiles = [UITile('w' + s.char.lower()) for s in Wind.all4]
self.board = Board(2, 2, self.tileset)
self.board.showShadows = True
self.tileScene.addItem(self.board)
diff --git a/src/uiwall.py b/src/uiwall.py
index 4b822e9..8c9422c 100644
--- a/src/uiwall.py
+++ b/src/uiwall.py
@@ -19,6 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
from common import Internal, ZValues, unicode
+from wind import East
from qt import QRectF, QPointF, QGraphicsSimpleTextItem, QFontMetrics
from board import PlayerWind, YellowText, Board, rotateCenter
@@ -111,7 +112,7 @@ class UIWall(Wall):
side.setParentItem(self.__square)
side.lightSource = self.lightSource
side.windTile = PlayerWind(
- 'E',
+ East,
Internal.scene.windTileset,
parent=side)
side.windTile.hide()
diff --git a/src/wind.py b/src/wind.py
new file mode 100644
index 0000000..50152d9
--- /dev/null
+++ b/src/wind.py
@@ -0,0 +1,143 @@
+# -*- 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 __future__ import print_function
+
+from common import unicode, bytes # pylint: disable=redefined-builtin
+
+# pylint: disable=invalid-name
+
+class Wind(object):
+ """we want to use an wind for indices.
+
+ char is the wind as a char (native string)
+ svgName is the name of the wind in the SVG files.
+ """
+ all = list()
+ all4 = list()
+
+ def __new__(cls, *args):
+ if not Wind.all:
+ Wind.all = list(object.__new__(cls) for cls in (_East, _South, _West, _North, _NoWind))
+ Wind.all4 = list(Wind.all[:4])
+ if len(args) == 1:
+ windIdent = args[0]
+ assert cls is Wind, '{}({}) is illegal'.format(cls.__name__, windIdent)
+ if isinstance(windIdent, bytes):
+ windIdx = b'eswn'.index(windIdent.lower())
+ else:
+ windIdx = u'eswn'.index(windIdent.lower())
+ return Wind.all[windIdx]
+ assert len(args) == 0 and cls is not Wind, 'Wind() must have exactly one argument'
+
+ for result in Wind.all:
+ if isinstance(result, cls):
+ return result
+ raise Exception('Wind.__new__ failed badly')
+
+ def __eq__(self, other):
+ if not other:
+ return False
+ if isinstance(other, self.__class__):
+ return True
+ if isinstance(other, Wind):
+ return False
+ try:
+ return str(self.char) == other.upper()
+ except AttributeError:
+ return False
+
+ def __gt__(self, other):
+ assert isinstance(other, Wind)
+ return self.__index__() > other.__index__()
+
+ def __lt__(self, other):
+ assert isinstance(other, Wind)
+ return self.__index__() < other.__index__()
+
+ def __ge__(self, other):
+ assert isinstance(other, Wind)
+ return self.__index__() >= other.__index__()
+
+ def __le__(self, other):
+ assert isinstance(other, Wind)
+ return self.__index__() <= other.__index__()
+
+ def __hash__(self):
+ return self.__index__()
+
+ def __str__(self):
+ return str(self.char)
+
+ def __unicode__(self):
+ return unicode(self.char)
+
+ def __repr__(self):
+ return str(self)
+
+class _East(Wind):
+ """East"""
+ char = 'E'
+ svgName = 'WIND_3'
+ tile = None
+ def __index__(self):
+ return 0
+
+class _South(Wind):
+ """South"""
+ char = 'S'
+ svgName = 'WIND_2'
+ tile = None
+
+ def __index__(self):
+ return 1
+
+class _West(Wind):
+ """West"""
+ char = 'W'
+ svgName = 'WIND_4'
+ tile = None
+
+ def __index__(self):
+ return 2
+
+class _North(Wind):
+ """North"""
+ char = 'N'
+ svgName = 'WIND_1'
+ tile = None
+
+ def __index__(self):
+ return 3
+
+class _NoWind(Wind):
+ """no wind"""
+ char = 'X'
+ svgName = None
+ tile = None
+
+ def __index__(self):
+ return 4
+
+East = _East()
+South = _South()
+West = _West()
+North = _North()
+