/usr/share/kde4/apps/kajongg/player.py is in kajongg 4:4.13.0-0ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 | # -*- 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.
"""
import weakref
from collections import defaultdict
from log import logException, logWarning, m18n, m18nc, m18nE
from common import WINDS, IntDict, Debug
from query import Query
from tile import Tile, TileList, elements
from meld import Meld, MeldList
from permutations import Permutations
from message import Message
from hand import Hand
from intelligence import AIDefault
class Players(list):
"""a list of players where the player can also be indexed by wind.
The position in the list defines the place on screen. First is on the
screen bottom, second on the right, third top, forth left"""
allNames = {}
allIds = {}
humanNames = {}
def __init__(self, players=None):
list.__init__(self)
if players:
self.extend(players)
def __getitem__(self, index):
"""allow access by idx or by wind"""
for player in self:
if player.wind == index:
return player
return list.__getitem__(self, index)
def __str__(self):
return ', '.join(list('%s: %s' % (x.name, x.wind) for x in self))
def byId(self, playerid):
"""lookup the player by id"""
for player in self:
if player.nameid == playerid:
return player
logException("no player has id %d" % playerid)
def byName(self, playerName):
"""lookup the player by name"""
for player in self:
if player.name == playerName:
return player
logException("no player has name %s - we have %s" % (playerName, [x.name for x in self]))
@staticmethod
def load():
"""load all defined players into self.allIds and self.allNames"""
Players.allIds = {}
Players.allNames = {}
for nameid, name in Query("select id,name from player").records:
Players.allIds[name] = nameid
Players.allNames[nameid] = name
if not name.startswith('Robot'):
Players.humanNames[nameid] = name
@staticmethod
def createIfUnknown(name):
"""create player in database if not there yet"""
if name not in Players.allNames.values():
Players.load() # maybe somebody else already added it
if name not in Players.allNames.values():
Query("insert or ignore into player(name) values(?)", (name,))
Players.load()
assert name in Players.allNames.values(), '%s not in %s' % (name, Players.allNames.values())
def translatePlayerNames(self, names):
"""for a list of names, translates those names which are english
player names into the local language"""
known = set(x.name for x in self)
return list(self.byName(x).localName if x in known else x for x in names)
class Player(object):
"""all player related attributes without GUI stuff.
concealedTiles: used during the hand for all concealed tiles, ungrouped.
concealedMelds: is empty during the hand, will be valid after end of hand,
containing the concealed melds as the player presents them."""
# pylint: disable=too-many-instance-attributes,too-many-public-methods
def __init__(self, game):
if game:
self._game = weakref.ref(game)
else:
self._game = None
self.__balance = 0
self.__payment = 0
self.wonCount = 0
self.__name = ''
self.wind = WINDS[0]
self.intelligence = AIDefault(self)
self.visibleTiles = IntDict(game.visibleTiles) if game else IntDict()
self.handCache = {}
self.cacheHits = 0
self.cacheMisses = 0
self.clearHand()
self.__lastSource = '1' # no source: blessing from heaven or earth
self.handBoard = None
def clearCache(self):
"""clears the cache with Hands"""
if Debug.hand and len(self.handCache):
self.game.debug('%s: cache hits:%d misses:%d' % (self, self.cacheHits, self.cacheMisses))
self.handCache.clear()
Permutations.cache.clear()
self.cacheHits = 0
self.cacheMisses = 0
@property
def name(self):
"""write once, read many"""
return self.__name
@name.setter
def name(self, value):
"""write once"""
assert self.__name == ''
assert value
self.__name = value
@property
def game(self):
"""hide the fact that this is a weakref"""
if self._game:
return self._game()
def clearHand(self):
"""clear player attributes concerning the current hand"""
self._concealedTiles = []
self._exposedMelds = []
self._concealedMelds = []
self._bonusTiles = []
self.discarded = []
self.visibleTiles.clear()
self.newHandContent = None
self.originalCallingHand = None
self.__lastTile = None
self.lastSource = '1'
self.lastMeld = Meld()
self.__mayWin = True
self.__payment = 0
self.originalCall = False
self.dangerousTiles = list()
self.claimedNoChoice = False
self.playedDangerous = False
self.usedDangerousFrom = None
self.isCalling = False
self.clearCache()
self._hand = None
@property
def lastTile(self):
"""temp for debugging"""
return self.__lastTile
@lastTile.setter
def lastTile(self, value):
"""temp for debugging"""
assert isinstance(value, (Tile, type(None))), value
self.__lastTile = value
def invalidateHand(self):
"""some source for the computation of current hand changed"""
self._hand = None
@property
def hand(self):
"""a readonly tuple"""
if not self._hand:
self._hand = self.computeHand()
return self._hand
@property
def bonusTiles(self):
"""a readonly tuple"""
return tuple(self._bonusTiles)
@property
def concealedTiles(self):
"""a readonly tuple"""
return tuple(self._concealedTiles)
@property
def exposedMelds(self):
"""a readonly tuple"""
return tuple(self._exposedMelds)
@property
def concealedMelds(self):
"""a readonly tuple"""
return tuple(self._concealedMelds)
@property
def mayWin(self):
"""winning possible?"""
return self.__mayWin
@mayWin.setter
def mayWin(self, value):
"""winning possible?"""
if self.__mayWin != value:
self.__mayWin = value
self._hand = None
@property
def lastSource(self):
"""the source of the last tile the player got"""
return self.__lastSource
@lastSource.setter
def lastSource(self, lastSource):
"""the source of the last tile the player got"""
self.__lastSource = lastSource
if lastSource == 'd' and not self.game.wall.living:
self.__lastSource = 'Z'
if lastSource == 'w' and not self.game.wall.living:
self.__lastSource = 'z'
@property
def nameid(self):
"""the name id of this player"""
return Players.allIds[self.name]
@property
def localName(self):
"""the localized name of this player"""
return m18nc('kajongg, name of robot player, to be translated', self.name)
@property
def handTotal(self):
"""the hand total of this player for the final scoring"""
if not self.game.winner:
return 0
else:
return self.hand.total()
@property
def balance(self):
"""the balance of this player"""
return self.__balance
@balance.setter
def balance(self, balance):
"""the balance of this player"""
self.__balance = balance
self.__payment = 0
def getsPayment(self, payment):
"""make a payment to this player"""
self.__balance += payment
self.__payment += payment
@property
def payment(self):
"""the payments for the current hand"""
return self.__payment
@payment.setter
def payment(self, payment):
"""the payments for the current hand"""
assert payment == 0
self.__payment = 0
def __repr__(self):
return u'{name:<10} {wind}'.format(name=self.name[:10], wind=self.wind)
def __unicode__(self):
return u'{name:<10} {wind}'.format(name=self.name[:10], wind=self.wind)
def pickedTile(self, deadEnd, tileName=None):
"""got a tile from wall"""
self.game.activePlayer = self
tile = self.game.wall.deal([tileName], deadEnd=deadEnd)[0]
if hasattr(tile, 'tile'):
self.lastTile = tile.tile
else:
self.lastTile = tile
self.addConcealedTiles([tile])
if deadEnd:
self.lastSource = 'e'
else:
self.game.lastDiscard = None
self.lastSource = 'w'
return self.lastTile
def removeTile(self, tile):
"""remove from my tiles"""
if tile.isBonus:
self._bonusTiles.remove(tile)
else:
try:
self._concealedTiles.remove(tile)
except ValueError:
raise Exception('removeTile(%s): tile not in concealed %s' % \
(tile, ''.join(self._concealedTiles)))
if tile is self.lastTile:
self.lastTile = None
self._hand = None
def addConcealedTiles(self, tiles, animated=False): # pylint: disable=unused-argument
"""add to my tiles"""
assert len(tiles)
for tile in tiles:
assert isinstance(tile, Tile), 'tile:%s' % tile
if tile.isBonus:
self._bonusTiles.append(tile)
else:
assert tile.isConcealed, '%s data=%s' % (tile, tiles)
self._concealedTiles.append(tile)
self._hand = None
def syncHandBoard(self, adding=None):
"""virtual: synchronize display"""
pass
def colorizeName(self):
"""virtual: colorize Name on wall"""
pass
def getsFocus(self, dummyResults=None):
"""virtual: player gets focus on his hand"""
pass
def mjString(self):
"""compile hand info into a string as needed by the scoring engine"""
declaration = 'a' if self.originalCall else ''
return ''.join(['m', self.lastSource, declaration])
def makeTileKnown(self, tileName):
"""used when somebody else discards a tile"""
assert not self._concealedTiles[0].isKnown
self._concealedTiles[0] = tileName
self._hand = None
def computeHand(self, withTile=None):
"""returns Hand for this player"""
assert not (self._concealedMelds and self._concealedTiles)
assert isinstance(self.lastTile, (Tile, type(None)))
assert isinstance(withTile, (Tile, type(None)))
melds = ['R' + ''.join(str(x) for x in sorted(self._concealedTiles))]
if withTile:
melds[0] += withTile
if melds[0] == 'R':
melds = melds[1:]
melds.extend(str(x) for x in self._exposedMelds)
melds.extend(str(x) for x in self._concealedMelds)
melds.extend(str(x) for x in self._bonusTiles)
melds.append(self.mjString())
if withTile or self.lastTile:
melds.append('L%s%s' % (withTile or self.lastTile, self.lastMeld if self.lastMeld else ''))
return Hand(self, ' '.join(melds))
def scoringString(self):
"""helper for HandBoard.__str__"""
if self._concealedMelds:
parts = [str(x) for x in self._concealedMelds + self._exposedMelds]
else:
parts = [''.join(self._concealedTiles)]
parts.extend([str(x) for x in self._exposedMelds])
parts.extend(str(x) for x in self._bonusTiles)
return ' '.join(parts)
def sortRulesByX(self, rules): # pylint: disable=no-self-use
"""if this game has a GUI, sort rules by GUI order"""
return rules
def others(self):
"""a list of the other 3 players"""
return (x for x in self.game.players if x != self)
def tileAvailable(self, tileName, hand):
"""a count of how often tileName might still appear in the game
supposing we have hand"""
lowerTile = tileName.exposed
upperTile = tileName.concealed
visible = self.game.discardedTiles.count([lowerTile])
if visible:
if hand.lenOffset == 0 and self.game.lastDiscard and lowerTile is self.game.lastDiscard.exposed:
# the last discarded one is available to us since we can claim it
visible -= 1
visible += sum(x.visibleTiles.count([lowerTile, upperTile]) for x in self.others())
visible += sum(x.exposed == lowerTile for x in hand.tiles)
return 4 - visible
def violatesOriginalCall(self, discard=None):
"""called if discarding discard violates the Original Call"""
if not self.originalCall or not self.mayWin:
return False
if self.lastTile.exposed != discard.exposed:
if Debug.originalCall:
self.game.debug('%s would violate OC with %s, lastTile=%s' % (self, discard, self.lastTile))
return True
return False
class PlayingPlayer(Player):
"""a player in a computer game as opposed to a ScoringPlayer"""
# pylint: disable=too-many-public-methods
# too many public methods
def __init__(self, game):
self.sayable = {} # recompute for each move, use as cache
Player.__init__(self, game)
def popupMsg(self, msg):
"""virtual: show popup on display"""
pass
def hidePopup(self):
"""virtual: hide popup on display"""
pass
def speak(self, txt):
"""only a visible playing player can speak"""
pass
def declaredMahJongg(self, concealed, withDiscard, lastTile, lastMeld):
"""player declared mah jongg. Determine last meld, show concealed tiles grouped to melds"""
melds = concealed[:]
self.game.winner = self
if withDiscard:
assert isinstance(withDiscard, Tile), withDiscard
PlayingPlayer.addConcealedTiles(self, [withDiscard]) # this should NOT invoke syncHandBoard
if self.lastSource != 'k': # robbed the kong
self.lastSource = 'd'
# the last claimed meld is exposed
assert lastMeld in melds, '%s: melds=%s lastMeld=%s lastTile=%s withDiscard=%s' % (
self._concealedTiles, melds, lastMeld, lastTile, withDiscard)
melds.remove(lastMeld)
lastTile = withDiscard.exposed
lastMeld = lastMeld.exposed
self._exposedMelds.append(lastMeld)
for tileName in lastMeld:
self.visibleTiles[tileName] += 1
self.lastTile = lastTile
self.lastMeld = lastMeld
self._concealedMelds = melds
self._concealedTiles = []
self._hand = None
def __possibleChows(self):
"""returns a unique list of lists with possible claimable chow combinations"""
if self.game.lastDiscard is None:
return []
exposedChows = [x for x in self._exposedMelds if x.isChow]
if len(exposedChows) >= self.game.ruleset.maxChows:
return []
tile = self.game.lastDiscard
within = TileList(self.concealedTiles[:])
within.append(tile)
return within.hasChows(tile)
def __possibleKongs(self):
"""returns a unique list of lists with possible kong combinations"""
kongs = []
if self == self.game.activePlayer:
# declaring a kong
for tileName in set([x for x in self._concealedTiles if not x.isBonus]):
if self._concealedTiles.count(tileName) == 4:
kongs.append(tileName.kong)
elif self._concealedTiles.count(tileName) == 1 and \
tileName.exposed.pung in self._exposedMelds:
# the result will be an exposed Kong but the 4th tile
# came from the wall, so we use the form aaaA
kongs.append(tileName.kong.exposedClaimed)
if self.game.lastDiscard:
# claiming a kong
discardTile = self.game.lastDiscard.concealed
if self._concealedTiles.count(discardTile) == 3:
# TODO: discard.kong.concealed is aAAa but we need AAAA
kongs.append(Meld(discardTile * 4))
return kongs
def __maySayChow(self):
"""returns answer arguments for the server if calling chow is possible.
returns the meld to be completed"""
if self == self.game.nextPlayer():
return self.__possibleChows()
def __maySayPung(self):
"""returns answer arguments for the server if calling pung is possible.
returns the meld to be completed"""
lastDiscard = self.game.lastDiscard
if self.game.lastDiscard:
assert lastDiscard.isConcealed, lastDiscard
if self.concealedTiles.count(lastDiscard) >= 2:
return lastDiscard.pung
def __maySayKong(self):
"""returns answer arguments for the server if calling or declaring kong is possible.
returns the meld to be completed or to be declared"""
return self.__possibleKongs()
def __maySayMahjongg(self, move):
"""returns answer arguments for the server if calling or declaring Mah Jongg is possible"""
game = self.game
if move.message == Message.DeclaredKong:
withDiscard = move.meld[0].concealed
elif move.message == Message.AskForClaims:
withDiscard = game.lastDiscard
else:
withDiscard = None
hand = self.computeHand(withTile=withDiscard)
if hand.won:
if Debug.robbingKong:
if move.message == Message.DeclaredKong:
game.debug('%s may rob the kong from %s/%s' % \
(self, move.player, move.exposedMeld))
if Debug.mahJongg:
game.debug('%s may say MJ:%s, active=%s' % (
self, list(x for x in game.players), game.activePlayer))
return MeldList(x for x in hand.melds if not x.isDeclared), withDiscard, hand.lastMeld
def __maySayOriginalCall(self):
"""returns True if Original Call is possible"""
for tileName in set(self.concealedTiles):
newHand = self.hand - tileName
if newHand.callingHands:
if Debug.originalCall:
self.game.debug('%s may say Original Call by discarding %s from %s' % (self, tileName, self.hand))
return True
def computeSayable(self, move, answers):
"""find out what the player can legally say with this hand"""
self.sayable = {}
for message in Message.defined.values():
self.sayable[message] = True
if Message.Pung in answers:
self.sayable[Message.Pung] = self.__maySayPung()
if Message.Chow in answers:
self.sayable[Message.Chow] = self.__maySayChow()
if Message.Kong in answers:
self.sayable[Message.Kong] = self.__maySayKong()
if Message.MahJongg in answers:
self.sayable[Message.MahJongg] = self.__maySayMahjongg(move)
if Message.OriginalCall in answers:
self.sayable[Message.OriginalCall] = self.__maySayOriginalCall()
def maybeDangerous(self, msg):
"""could answering with msg lead to dangerous game?
If so return a list of resulting melds
where a meld is represented by a list of 2char strings"""
result = []
if msg in (Message.Chow, Message.Pung, Message.Kong):
possibleMelds = self.sayable[msg]
if isinstance(possibleMelds[0], basestring):
possibleMelds = [possibleMelds]
result = [x for x in possibleMelds if self.mustPlayDangerous(x)]
return result
def hasConcealedTiles(self, tiles, within=None):
"""do I have those concealed tiles?"""
if within is None:
within = self._concealedTiles
within = within[:]
for tile in tiles:
if tile not in within:
return False
within.remove(tile)
return True
def showConcealedTiles(self, tiles, show=True):
"""show or hide tiles"""
if not self.game.playOpen and self != self.game.myself:
if not isinstance(tiles, (list, tuple)):
tiles = [tiles]
assert len(tiles) <= len(self._concealedTiles), \
'%s: showConcealedTiles %s, we have only %s' % (self, tiles, self._concealedTiles)
for tileName in tiles:
src, dst = (Tile.unknown, tileName) if show else (tileName, Tile.unknown)
assert src != dst, (self, src, dst, tiles, self._concealedTiles)
if not src in self._concealedTiles:
logException('%s: showConcealedTiles(%s): %s not in %s.' % \
(self, tiles, src, self._concealedTiles))
idx = self._concealedTiles.index(src)
self._concealedTiles[idx] = dst
if self.lastTile and not self.lastTile.isKnown:
self.lastTile = None
self._hand = None
self.syncHandBoard()
def showConcealedMelds(self, concealedMelds, ignoreDiscard=None):
"""the server tells how the winner shows and melds his
concealed tiles. In case of error, return message and arguments"""
for meld in concealedMelds:
for tile in meld:
if tile == ignoreDiscard:
ignoreDiscard = None
else:
if not tile in self._concealedTiles:
msg = m18nE('%1 claiming MahJongg: She does not really have tile %2')
return msg, self.name, tile
self._concealedTiles.remove(tile)
if meld.isConcealed and not meld.isKong:
self._concealedMelds.append(meld)
else:
self._exposedMelds.append(meld)
if self._concealedTiles:
msg = m18nE('%1 claiming MahJongg: She did not pass all concealed tiles to the server')
return msg, self.name
self._hand = None
def robTile(self, tile):
"""used for robbing the kong"""
assert tile.isConcealed
tile = tile.exposed
for meld in self._exposedMelds:
if tile in meld:
meld = meld.without(tile)
self.visibleTiles[tile] -= 1
break
else:
raise Exception('robTile: no meld found with %s' % tile)
self.game.lastDiscard = tile.concealed
self.lastTile = None # our lastTile has just been robbed
self._hand = None
def scoreMatchesServer(self, score):
"""do we compute the same score as the server does?"""
if score is None:
return True
if any(not x.isKnown for x in self._concealedTiles):
return True
if str(self.hand) == score:
return True
self.game.debug('%s localScore:%s' % (self, self.hand))
self.game.debug('%s serverScore:%s' % (self, score))
logWarning('Game %s: client and server disagree about scoring, see logfile for details' % self.game.seed)
return False
def mustPlayDangerous(self, exposing=None):
"""returns True if the player has no choice, otherwise False.
Exposing may be a meld which will be exposed before we might
play dangerous"""
if self == self.game.activePlayer and exposing and len(exposing) == 4:
# declaring a kong is never dangerous because we get
# an unknown replacement
return False
afterExposed = list(x.exposed for x in self._concealedTiles)
if exposing:
exposing = exposing[:]
if self.game.lastDiscard:
# if this is about claiming a discarded tile, ignore it
# the player who discarded it is responsible
exposing.remove(self.game.lastDiscard)
for tile in exposing:
if tile.exposed in afterExposed:
# the "if" is needed for claimed pung
afterExposed.remove(tile.exposed)
return all(self.game.dangerousFor(self, x) for x in afterExposed)
def exposeMeld(self, meldTiles, calledTile=None):
"""exposes a meld with meldTiles: removes them from concealedTiles,
adds the meld to exposedMelds and returns it
calledTile: we got the last tile for the meld from discarded, otherwise
from the wall"""
game = self.game
game.activePlayer = self
allMeldTiles = meldTiles[:]
if calledTile:
assert isinstance(calledTile, Tile), calledTile
allMeldTiles.append(calledTile)
if len(allMeldTiles) == 4 and allMeldTiles[0].isExposed:
tile0 = allMeldTiles[0].exposed
# we are adding a 4th tile to an exposed pung
self._exposedMelds = [x for x in self._exposedMelds if x != tile0.pung]
meld = tile0.kong
if allMeldTiles[3] not in self._concealedTiles:
game.debug('t3 %s not in conc %s' % (allMeldTiles[3], self._concealedTiles))
self._concealedTiles.remove(allMeldTiles[3])
self.visibleTiles[tile0] += 1
else:
allMeldTiles = sorted(allMeldTiles) # needed for Chow
meld = Meld(allMeldTiles)
for meldTile in meldTiles:
self._concealedTiles.remove(meldTile)
for meldTile in allMeldTiles:
self.visibleTiles[meldTile.exposed] += 1
meld = meld.exposedClaimed if calledTile else meld.declared
if self.lastTile in allMeldTiles:
self.lastTile = self.lastTile.exposed
self._exposedMelds.append(meld)
self._hand = None
game.computeDangerous(self)
return meld
def findDangerousTiles(self):
"""update the list of dangerous tile"""
pName = self.localName
dangerous = list()
expMeldCount = len(self._exposedMelds)
if expMeldCount >= 3:
if all(x in elements.greenHandTiles for x in self.visibleTiles):
dangerous.append((elements.greenHandTiles,
m18n('Player %1 has 3 or 4 exposed melds, all are green', pName)))
group = defaultdict.keys(self.visibleTiles)[0].group
# see http://www.logilab.org/ticket/23986
assert group.islower(), self.visibleTiles
if group in Tile.colors:
if all(x.group == group for x in self.visibleTiles):
suitTiles = set([Tile(group, x) for x in Tile.numbers])
if self.visibleTiles.count(suitTiles) >= 9:
dangerous.append((suitTiles, m18n('Player %1 may try a True Color Game', pName)))
elif all(x.value in Tile.terminals for x in self.visibleTiles):
dangerous.append((elements.terminals,
m18n('Player %1 may try an All Terminals Game', pName)))
if expMeldCount >= 2:
windMelds = sum(self.visibleTiles[x] >= 3 for x in elements.winds)
dragonMelds = sum(self.visibleTiles[x] >= 3 for x in elements.dragons)
windsDangerous = dragonsDangerous = False
if windMelds + dragonMelds == expMeldCount and expMeldCount >= 3:
windsDangerous = dragonsDangerous = True
windsDangerous = windsDangerous or windMelds >= 3
dragonsDangerous = dragonsDangerous or dragonMelds >= 2
if windsDangerous:
dangerous.append((set(x for x in elements.winds if x not in self.visibleTiles),
m18n('Player %1 exposed many winds', pName)))
if dragonsDangerous:
dangerous.append((set(x for x in elements.dragons if x not in self.visibleTiles),
m18n('Player %1 exposed many dragons', pName)))
self.dangerousTiles = dangerous
if dangerous and Debug.dangerousGame:
self.game.debug('dangerous:%s' % dangerous)
|